3.7.6. Reprezentace výrazu jako obecného stromu

V předchozí části jsme se zabývali spíše homogenními stromovými strukturami, kdy všechny typy uzlů stromu byly shodné. V praxi však mohou nastat i situace, kdy například typ listu je jiný než typ vnitřních uzlů, nebo kdy je strom tvořen různými typy uzlů, jejichž větvení je pak dáno konkrétním typem uzlu.

Typickým příkladem použití obecného stromu je reprezentace aritmetického výrazu. Každý aritmetický výraz můžeme znázornit stromovou strukturou, ve které vnitřní uzly představují operátory (počet následníků je pak dán aritou operace) a listy představují operandy. Například výraz 2 + 3 * 6 můžeme reprezentovat stromem uvedeným na Obr. 3.13.

Obrázek 3.13. Stromová reprezentace výrazu 2+3*6

Pro reprezentaci výrazu, zejména pokud budeme uvažovat nejvýše binární operátory, bychom sice mohli použít třídy TreeNode, kterou jsme si definovali v předchozích částech, ovšem manipulace s takto reprezentovanými uzly stromu by byla obtížná. Zejména bychom museli pro přístup k datové složce uzlu stále používat přetypování a nemohli bychom zajistit typovou kontrolu našeho programu v době překladu. Lepším řešením může tedy být vlastní stromová struktura, znázorněná na Obr. 3.14.

Obrázek 3.14. Struktura tříd pro reprezentaci výrazu

Základem reprezentace výrazu je abstraktní třída Expr, která představuje libovolný výraz. Tento výraz můžeme specializovat na výraz tvořený unárním operátorem (UnaryExpr) a výraz tvořený binárním operátorem (BinaryExpr). Budeme-li pro jednoduchost uvažovat pouze výrazy tvořené celočíselnými operandy a operátory pro změnu znaménka, sčítání a násobení, získáme čtyři listové třídy ConstExpr, NegExpr, AddExpr a MulExpr. Takto definovaná struktura tříd umožní rozšiřování o další druhy operandů a operátorů, aniž by bylo třeba přepisovat existující definice. Třídy reprezentující konkrétní operandy a operátory také implementují metodu toString(), aby bylo možné výraz v čitelné podobě zobrazit. Definice tříd z Obr. 3.14 je následující:

abstract class Expr { } 
 
abstract class UnaryExpr extends Expr { 
   public UnaryExpr(Expr left) {  
      this.left = left;  
   } 
   public Expr getLeft() { return left; } 
   protected Expr left; 
} 
 
abstract class BinaryExpr extends Expr { 
   public BinaryExpr(Expr left, Expr right) { 
      this.left = left; 
      this.right = right; 
   } 
   public Expr getLeft() { return left; } 
   public Expr getRight() { return right; } 
   protected Expr left; 
   protected Expr right; 
} 
 
class ConstExpr extends Expr { 
   public ConstExpr(int value) { this.value = value; } 
   public int getValue() { return value; } 
   public String toString() { return Integer.toString(value); } 
   protected int value; 
} 
 
class NegExpr extends UnaryExpr { 
   public NegExpr(Expr left) { super(left); } 
   public String toString() {  
      return "(-" + left + ")";  
   } 
} 
 
class AddExpr extends BinaryExpr { 
   public AddExpr(Expr left, Expr right) { super(left,right); } 
   public String toString() {  
      return "(" + left + "+" + right + ")";  
   } 
} 
 
class MulExpr extends BinaryExpr { 
   public MulExpr(Expr left, Expr right) { super(left,right); } 
   public String toString() {  
      return "(" + left + "*" + right + ")";  
   } 
}

Reprezentaci výrazu 2+3*6 pak vytvoříme a vypíšeme na standardní výstup touto posloupností příkazů:

Expr expr =  
   new AddExpr( 
      new ConstExpr(2), 
      new MulExpr( 
         new ConstExpr(3), 
         new ConstExpr(6))); 
System.out.println(expr);