我是Java新手并尝试将evaluate方法添加到我的课程中。 ExpTree类及其测试程序是给我的。我在课堂上学到了我的代码,但不知道为什么它不起作用。
evaluate()方法,返回ExpTree的算术评估。这应该以递归方式完成,因此您需要2种方法来完成。在它将导致除法或mod为0的情况下,它应该抛出一个带有描述性String的新ArithmeticException。如果树为空,则evaluate()也应该抛出一个带有描述性字符串的新ArithmeticException。
这是我的代码:
// This will implement an "Expression Tree" which stores an arithmetic expression
import java.util.*;
public class ExpTree
{
//-------data
private ExpNode root;
//-------constructor
public ExpTree()
{
root = null;
}
//constructor where a string is passed in. It is parsed and stored
public ExpTree(String expString)
{
//declare StringTokenizer, Stacks, and other variables used in parsing
StringTokenizer tokenizer = new StringTokenizer (expString, "()+-*/%", true);
String token;
ExpNode operator, leftOperand, rightOperand;
Stack<ExpNode> operators = new Stack<ExpNode>();
Stack<ExpNode> operands = new Stack<ExpNode>();
//break up expString into tokens
while (tokenizer.hasMoreTokens())
{
token = tokenizer.nextToken();
// if the current token is a left paren, ignore it
if (token.equals ("("))
;
// if the current token is an operator, put it on the
// operator stack
else if ((token.equals ("+")) || (token.equals ("-")) ||
(token.equals ("*")) || (token.equals ("/")) || (token.equals ("%")))
operators.push (new ExpNode(token));
//if the current token is a right paren, pop the operators stack
//to get the operator, pop the operands stack twice to get the two
//operands (stored as expression trees). Then make the two operands
//children of the operator and push back on the operands tree.
else if (token.equals (")"))
{
operator = operators.pop();
rightOperand = operands.pop();
leftOperand = operands.pop();
operator.setLeft(leftOperand);
operator.setRight(rightOperand);
operands.push(operator);
}
//otherwise, the token should be a number - put it in the operands stack
else
operands.push (new ExpNode(token));
} // while (tokenizer.hasMoreTokens())
//when finished parsing, the operands stack should contain the fully-built
//expression tree.
if (!operands.isEmpty())
root = operands.pop();
}
//-------methods
//isEmpty()
public boolean isEmpty()
{
return (root == null);
}
//printTree methods - prints the tree in RNL order, with indents. Called from "outside"
public void printTree()
{
if (root == null)
System.out.println("The tree is empty");
else
printTree(root, 0); //start with the root with 0 indentations
}
//recursive, private version of printTree
private void printTree(ExpNode subTree, int indents)
{
//if there is a right side, handle it first (with 1 more indent)
if (subTree.getRight() != null)
printTree(subTree.getRight(), indents+1);
//then print the node itself (first move over the right amount of indents)
System.out.println("\n\n\n");
for (int i=0; i<indents; i++)
System.out.print("\t");
System.out.println(subTree);
//if there is a left side, handle it first (with 1 more indent)
if (subTree.getLeft() != null)
printTree(subTree.getLeft(), indents+1);
}
//inorder traversal - starts the recursive calls to print inorder
public String inOrder()
{
return inOrder(root);
}
//inorder traversal - recursive left side of tree, print node, right side of tree
private String inOrder(ExpNode theTreeToTraverse)
{
if (theTreeToTraverse == null)
return ""; //don't try to do anything if tree is null
//else build up a String to return. It will involve recursive calls
String returnString = "";
if (theTreeToTraverse.getLeft() != null)
{
returnString += "(" + inOrder(theTreeToTraverse.getLeft());
}
returnString += theTreeToTraverse;
if (theTreeToTraverse.getRight() != null)
{
returnString += inOrder(theTreeToTraverse.getRight()) + ")";
}
return returnString;
}
//public version of evaluate
public double evaluate(){
if (root == null) //Am I null?
throw new ArithmeticException("The tree is empty, nothing to be evaluated!");
else //You handle it!
return recursiveEvaluate(root);
}
//Recursive version of evaluate
private double recursiveEvaluate(ExpNode subTree){
//If subTree is empty
if (subTree == null)
return 0;
//What are you subTree? A number? An operator?
else if(subTree.getData().equals("+"))
return recursiveEvaluate(subTree.getLeft()) +
recursiveEvaluate(subTree.getRight()) ;
else if(subTree.getData().equals("-"))
return recursiveEvaluate(subTree.getLeft()) -
recursiveEvaluate(subTree.getRight()) ;
else if(subTree.getData().equals("*"))
return recursiveEvaluate(subTree.getLeft()) *
recursiveEvaluate(subTree.getRight()) ;
else if(subTree.getData().equals("/")){
double right = recursiveEvaluate(subTree.getRight());
if(right == 0.0)
throw new ArithmeticException("Divide by zero is undefined!");
return recursiveEvaluate(subTree.getLeft()) / right;
}
else if(subTree.getData().equals("%")){
double right = recursiveEvaluate(subTree.getRight());
if(right == 0.0)
throw new ArithmeticException("Mod by zero exception");
return recursiveEvaluate(subTree.getLeft()) % right;
}
//Converting String type to double
else
return Double.parseDouble(subTree.getData());
}
//Public version of numPlus
public int numPlus(){
return recursiveNumPlus(root);
}
//Recursive version of numPlus
private int recursiveNumPlus(ExpNode subTree){
if (subTree == null)
return 0;
//If you are a '+' sign
if(subTree.getData().equals("+"))
return recursiveNumPlus(subTree.getLeft()) +
recursiveNumPlus(subTree.getRight()) + 1;
else
return recursiveNumPlus(subTree.getLeft()) +
recursiveNumPlus(subTree.getRight());
}
}
//***************************************************************************
// ExpNode holds a "node" for an ExpTree.
class ExpNode
{
//data
private String data;
private ExpNode left;
private ExpNode right;
//constructor
public ExpNode(String el)
{
data = el;
left = right = null;
}
//methods
//toString() - this is how an ExpNode represents itself as a String
public String toString()
{
return data;
}
//getLeft - returns the reference to the left subTree
public ExpNode getLeft()
{
return left;
}
//getRight - returns the reference to the right subTree
public ExpNode getRight()
{
return right;
}
//getData - returns the data (could be an operator or a number, so returns as a String)
public String getData()
{
return data;
}
//setLeft - sets the left subTree to whatever is passed in
public void setLeft(ExpNode newNode)
{
left = newNode;
}
//setRight - sets the right subTree to whatever is passed in
public void setRight(ExpNode newNode)
{
right = newNode;
}
}
答案 0 :(得分:1)
面向对象的方法是为每种节点定义一个专用类型。为了保持这个答案的长度合理并避免做功课,我只会给出一个仅涉及加法和乘法的整数表达式的最小例子。
第一步是定义表达式节点必须提供的内容。为此,我们定义了接口ExprNode
。如果你还没有在你的班级中学习多态性(这应该让我感到惊讶),你可能现在想停止阅读并在你了解它之后再回来。
我们想要评估节点,因此我们将添加一个evaluate
方法,该方法应该返回以该节点为根的子表达式的值。我们将其实现推迟到特定的节点类,因为它们最了解如何评估自己。
我们还想格式化表达式,因此我们将添加另一种方法来使用中缀表示法格式化子表达式。
public interface ExprNode {
int evaluate();
String asInfixString();
}
现在,让我们看看我们需要什么节点。当然,任何表达式都会在叶子中包含数字,所以我们最好开始为它们定义一个类。 ValueNode
的实现非常简单,不是说微不足道。
public final class ValueNode implements ExprNode {
private final int value;
public ValueNode(final int value) {
this.value = value;
}
@Override
public int evaluate() {
return this.value;
}
@Override
public String asInfixString() {
return String.valueOf(this.value);
}
}
接下来,我们有两个二进制操作+
和*
。各个类的实现再次非常简单。
public final class PlusNode implements ExprNode {
private final ExprNode lhs;
private final ExprNode rhs;
public PlusNode(final ExprNode lhs, final ExprNode rhs) {
this.lhs = lhs;
this.rhs = rhs;
}
@Override
public int evaluate() {
return this.lhs.evaluate() + this.rhs.evaluate();
}
@Override
public String asInfixString() {
return String.format("(%s) + (%s)",
this.lhs.asInfixString(),
this.rhs.asInfixString());
}
}
public final class TimesNode implements ExprNode {
private final ExprNode lhs;
private final ExprNode rhs;
public TimesNode(final ExprNode lhs, final ExprNode rhs) {
this.lhs = lhs;
this.rhs = rhs;
}
@Override
public int evaluate() {
return this.lhs.evaluate() * this.rhs.evaluate();
}
@Override
public String asInfixString() {
return String.format("(%s) * (%s)",
this.lhs.asInfixString(),
this.rhs.asInfixString());
}
}
配备上,我们可以优雅地构建表达树,打印和评估它们。以下是2 * (3 + 4)
表达式的示例。
ExprNode expr = new TimesNode(
new ValueNode(2),
new PlusNode(new ValueNode(3), new ValueNode(4)));
System.out.println(expr.asInfixString() + " = " + expr.evaluate());
它会打印(2) * ((3) + (4)) = 14
。
因此,对于您的ExprTree
,您只需检查root != null
是否为return root.evaluate()
。
如果我们想要更多表达式怎么办?
显然,我们将定义另一个ExprNode
的子类型。例如,我们可以定义更多二元运算符来处理减法和除法,另一个一元节点用于一元减号等等。这些类中的每一个都必须实现由ExprNode
指定的接口,因此任何子类都可以以相同的方式使用,封装逻辑如何评估自身。
如果我们想要更多操作怎么办?
例如,我们可能希望以后缀表示法格式化表达式。为此,我们可以向asPostfixString
添加另一种方法ExprNode
。但是,这有点尴尬,因为这意味着我们必须去编辑到目前为止已经实现的所有子类,添加新操作。
这是一个相当基本的问题。如果您布置复合结构来封装节点中的操作,那么它使用起来很简单,并且添加新节点类型很简单但很难添加模式操作。如果您在每个操作中使用案例选择(有点像在您的代码中)那么添加新操作会更简单,但操作代码会变得复杂并且很难添加更多节点类型(所有操作的代码都需要被改变了。这种困境被称为tyranny of the dominant model decomposition。 visitor pattern试图摆脱它。
无论如何,如果您正在学习基本的Java类,我认为您应该学习的是实现一个多态树,其节点在节点中定义,如上所示。