字符串运算公式求值问题

String str = "1+2-3*4/(1-5)";
怎么样通过这个字符串得到该公式算出来的值?
[b]问题补充:[/b]
请问,我定义
Stack stack = new Stack()
这个的时候,怎么会出错了?
[b]问题补充:[/b]
List list
我这样定义的时候,出现这样的错误 expented
我用了jre1.5了,也import 了
[b]问题补充:[/b]
public class test1 {
public static void main(String[] args) {
test1 test11 = new test1();
List list;
}
}
这样出现了not a statement的错误,我已经在juild配置了jdk1.5了,也import了。不知道为什么还有这样的错误?
[b]问题补充:[/b]
我用的是JBuilder X版本,JDK配置的是jdk1.5.0_12
就是不知道为什么出错,真郁闷~
[b]问题补充:[/b]
还是不行啊。都不知道什么原因。。
Eclipse没用过,一直都用jbuild。。
不过还是谢谢RednaxelaFX兄台了。呵呵~~

你编译一下这个文件看看:
Test.java:
[code="java"]import java.util.*;

public class Test {
public static void main(String[] args) {
List list;
}
}[/code]
如果环境正确,那这段代码不应该出错。如果编译这段代码没出错而编译你自己写的代码出错了,那对比一下看看问题到底是什么。或者把你出问题的代码整个贴出来,不然无从判断什么地方有问题。

稍微再改改……难怪优先级相等的时候不对劲,原来我少写了个等号。
[code="java"]import java.util.Scanner;
import java.util.Stack;

public class SimpleCalculator {
/**
* Evaluate an arithmetic expression, and return the result as a double.
* @param input the expression to evaluate.
* @return the evaluated result.
*/
public double evaluate(String input) {
initialize();

    this.scanner = new Scanner(input);
    this.scanner.useDelimiter("\\s+|(?=[.0-9])(?<![.0-9])|(?![.0-9])(?<=[.0-9])|(?![.0-9])(?<![.0-9])");

    Token currentToken = nextToken();
    Token t = null;
    while (null != currentToken) {
        switch (currentToken.getKind()) {
        case NUMBER:
            // Simply push number tokens onto the evaluation stack.
            this.eval.push(currentToken.getValue());
            break;
        case LPAREN:
            // Simply push left parenthesis tokens onto the operator stack.
            this.ops.push(currentToken);
            break;
        case RPAREN:
            // Until a left parenthesis pops off the operator stack, keep
            // poping operators and execute them.
            // If the stack becomes empty without a matching left parenthesis,
            // the expression must have syntax errors.
            for (t = this.ops.pop();
                 TokenKind.LPAREN != t.getKind();
                 t = this.ops.pop()) {
                if (ops.empty()) throw new Error("Syntax Error: unmatched parenthesis");

                doOperation(t);
            }
            break;
        default:
            // For binary arithmetic operators, keep poping operators whose binding power
            // is less or equal to the current token's and execute them; after that push
            // the current token onto the operator stack.
            if (!ops.empty()) {
                for (t = this.ops.pop();
                     currentToken.getKind().getBindingPower() <= t.getKind().getBindingPower();
                     t = this.ops.pop()) {                        
                    doOperation(t);
                    if (this.ops.empty()) {
                        t = null;
                        break;
                    }
                }
            }
            if (null != t) ops.push(t);
            ops.push(currentToken);
            break;
        }

        // reinitialize
        currentToken = nextToken();
    }

    // execute remaining operators on stack
    while (!ops.empty()) {
        t = this.ops.pop();
        doOperation(t);
    }

    // the result is on the top of evaluation stack,
    // pop it off and return the result.
    return this.eval.pop();
}

/*
 * Initialize the evaluation and operator stacks.
 */
private void initialize() {
    if (null == this.eval) this.eval = new Stack<Double>();
    if (null == this.ops) this.ops = new Stack<Token>();
    this.eval.clear();
    this.ops.clear();
}

/*
 * Return the next token from the input expression.
 * The token returned will be associated with its numeric value,
 * if and only if the token is a number.
 */
private Token nextToken() {
    Token t = null;
    if (this.scanner.hasNextDouble()) {
        t = new Token(TokenKind.NUMBER, this.scanner.nextDouble());
    } else if (this.scanner.hasNext()) {
        String s = this.scanner.next("[-+*/()]");
        if ("+".equals(s)) {
            t = new Token(TokenKind.ADD);
        } else if ("-".equals(s)) {
            t = new Token(TokenKind.SUBTRACT);
        } else if ("*".equals(s)) {
            t = new Token(TokenKind.MULTIPLY);
        } else if ("/".equals(s)) {
            t = new Token(TokenKind.DIVIDE);
        } else if ("(".equals(s)) {
            t = new Token(TokenKind.LPAREN);
        } else if (")".equals(s)) {
            t = new Token(TokenKind.RPAREN);
        }
    }
    return t;
}

/*
 * Execute a binary arithmetic operation.
 * Pop the top two values off the evaluation stack, do the operation,
 * and then push the result back onto the evaluation stack.
 */
private void doOperation(Token t) {
    double y = this.eval.pop();
    double x = this.eval.pop();
    double temp = t.getKind().doOperation(x, y);
    this.eval.push(temp);
}

/*
 * Tokenizer for the input expression.
 */
private Scanner scanner;

/*
 * Evaluation stack.
 */
private Stack<Double> eval;

/*
 * Operator stack, for converting infix expression to postfix expression.
 */
private Stack<Token> ops;

public static void main(String[] args) {
    if (args.length < 1) {
        System.err.println("Usage: java SimpleCalculator <expression>");
        System.exit(1);
    }

    SimpleCalculator calc = new SimpleCalculator();
    double result = calc.evaluate(args[0]);
    System.out.println(result);
}

}

enum TokenKind {
// operators
ADD(1) {
public double doOperation(double x, double y) {
return x + y;
}
},
SUBTRACT(1) {
public double doOperation(double x, double y) {
return x - y;
}
},
MULTIPLY(2) {
public double doOperation(double x, double y) {
return x * y;
}
},
DIVIDE(3) {
public double doOperation(double x, double y) {
return x / y;
}
},

// punctuation
LPAREN(0),
RPAREN(0),

// number
NUMBER(0);

TokenKind(int bindingPower) {
    this.bindingPower = bindingPower;
}

public int getBindingPower() {
    return this.bindingPower;
}

public double doOperation(double x, double y) {
    return Double.NaN; // dummy, operation not supported
}

private int bindingPower;

}

class Token {
public Token(TokenKind kind) {
this(kind, Double.NaN);
}

public Token(TokenKind kind, double value) {
    this.kind = kind;
    this.value = value;
}

public TokenKind getKind() {
    return this.kind;
}

public double getValue() {
    return this.value;
}

private TokenKind kind;
private double value;

}[/code]

如果只有四则混合运算,也就是说只有左结合的二元运算符的话,像这样就可以了:
SimpleCalculator.java
[code="java"]import java.util.Scanner;
import java.util.Stack;

public class SimpleCalculator {
public double calculate(String input) {
initialize();

    this.scanner = new Scanner(input);
    this.scanner.useDelimiter("\\s+|(?=[.0-9])(?<![.0-9])|(?![.0-9])(?<=[.0-9])|(?![.0-9])(?<![.0-9])");
    Token currentToken = nextToken();
    Token t = null;
    while (null != currentToken) {
        switch (currentToken.getKind()) {
        case NUMBER:
            this.eval.push(currentToken.getValue());
            break;
        case LPAREN:
            this.ops.push(currentToken);
            break;
        case RPAREN:
            for (t = this.ops.pop();
                 TokenKind.LPAREN != t.getKind();
                 t = this.ops.pop()) {
                if (ops.empty()) throw new Error("Syntax Error: unmatched parenthesis");

                doOperation(t);
            }
            break;
        default:
            if (!ops.empty()) {
                for (t = this.ops.pop();
                     currentToken.getKind().getPriority() < t.getKind().getPriority();
                     t = this.ops.pop()) {                        
                    doOperation(t);
                    if (this.ops.empty()) {
                        t = null;
                        break;
                    }
                }
            }
            if (null != t) ops.push(t);
            ops.push(currentToken);
            break;
        }

        // reinitialize
        currentToken = nextToken();
    }
    while (!ops.empty()) {
        t = this.ops.pop();
        doOperation(t);
    }

    return this.eval.pop();
}

private void initialize() {
    if (null == this.eval) this.eval = new Stack<Double>();
    if (null == this.ops) this.ops = new Stack<Token>();
    this.eval.clear();
    this.ops.clear();
}

private Token nextToken() {
    Token t = null;
    if (this.scanner.hasNextDouble()) {
        t = new Token(TokenKind.NUMBER, this.scanner.nextDouble());
    } else if (this.scanner.hasNext()) {
        String s = this.scanner.next("[-+*/()]");
        if ("+".equals(s)) {
            t = new Token(TokenKind.ADD);
        } else if ("-".equals(s)) {
            t = new Token(TokenKind.SUBTRACT);
        } else if ("*".equals(s)) {
            t = new Token(TokenKind.MULTIPLY);
        } else if ("/".equals(s)) {
            t = new Token(TokenKind.DIVIDE);
        } else if ("(".equals(s)) {
            t = new Token(TokenKind.LPAREN);
        } else if (")".equals(s)) {
            t = new Token(TokenKind.RPAREN);
        }
    }
    return t;
}

private void doOperation(Token t) {
    double y = this.eval.pop();
    double x = this.eval.pop();
    double temp = t.getKind().doOperation(x, y);
    this.eval.push(temp);
}

private Scanner scanner;
private Stack<Double> eval;
private Stack<Token> ops;

public static void main(String[] args) {
    if (args.length < 1) {
        System.err.println("Usage: java SimpleCalculator <expression>");
        System.exit(1);
    }

    SimpleCalculator calc = new SimpleCalculator();
    double result = calc.calculate(args[0]);
    System.out.println(result);
}

}

enum TokenKind {
// operators
ADD(1) {
public double doOperation(double x, double y) {
return x + y;
}
},
SUBTRACT(1) {
public double doOperation(double x, double y) {
return x - y;
}
},
MULTIPLY(2) {
public double doOperation(double x, double y) {
return x * y;
}
},
DIVIDE(2) {
public double doOperation(double x, double y) {
return x / y;
}
},

// punctuation
LPAREN(0),
RPAREN(0),

// number
NUMBER(0);

TokenKind(int priority) {
    this.priority = priority;
}

public int getPriority() {
    return this.priority;
}

public double doOperation(double x, double y) {
    return Double.NaN; // dummy, operation not supported
}

private int priority;

}

class Token {
public Token(TokenKind kind) {
this(kind, Double.NaN);
}

public Token(TokenKind kind, double value) {
    this.kind = kind;
    this.value = value;
}

public TokenKind getKind() {
    return this.kind;
}

public double getValue() {
    return this.value;
}

private TokenKind kind;
private double value;

}[/code]

原理就是一边将一个中序表达式转换成后序表达式,一边计算中间结果。
转换的算法是使用一个运算符栈,根据运算符的优先等级来判断输出顺序。这种算法也称为Shunting yard算法。
计算中间结果是利用另外一个栈来保存中间结果;每当遇到一个运算符,将栈顶的两个值抛出来,计算出中间结果后再压回到栈上。

如果要求值的表达式中的运算符不只是左结合的二元运算符,而包括一元、右结合等属性的运算符事,上面的代码就需要稍微添加一些内容才能处理(例如说“优先级”得分为左优先级和右优先级)。

另外,要通过递归下降的方式来解析并求值也很容易;甚至用上解析器生成器都可以。

参考这个帖子
[url]http://fuliang.iteye.com/blog/172233[/url]
类似于写一个js的eval函数

嗯,楼上链接里的实现跟我在一楼的实现原理都一样,都是infix expression -> postfix expression然后计算数值。主要区别是我的代码是在一趟里同时完成转换与计算,而楼上链接里的是先用一趟完成转换,然后再计算转换后的表达式。
我上面的代码也只是看到这问题之后随便写的,没怎么考虑类设计之类的。代码比较难看,见笑了。
除了趟数的不同之外,我的代码使用了更多Java标准库里现成的工具,例如使用Scanner来做词法分析,又或者是利用enum的语法结构来省去一个switch之类的。都只是些细节的差异来的。多了解标准库里已有的功能并在自己的代码中使用它们,无疑能让自己的代码更加简洁。例如楼上链接里的代码的这个方法:
[code="java"]private boolean isOperator(String str){
return str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/");
}[/code]
把四个调用合并成一个调用,效果也是一样的:
[code="java"]private boolean isOperator(String str){
return "+-*/".contains(str);
}[/code]

而楼上链接里的doEval()方法里的stack,仔细观察会发现它是所谓的“求值栈”(evaluation stack),只用于保存计算的中间结果。既然如此就不必使用Stack,根据那帖里其它部分的代码,只需要Stack就可以了。那么原本的:
[code="java"]private int doEval(List list) {
Stack stack = new Stack();
String element;
int n1,n2,result;
try {
for(int i = 0; i < list.size();i++) {
element = list.get(i);
if(isOperator(element)) {
n1 = Integer.parseInt(stack.pop());
n2 = Integer.parseInt(stack.pop());
result = doOperate(n1,n2,element);
stack.push(new Integer(result).toString());
} else {
stack.push(element);
}
}
return Integer.parseInt(stack.pop());
} catch (RuntimeException e) {
throw new IllegalExpressionException(e.getMessage());

}
}[/code]
(注意到原本这个方法的实现其实是错的,把n1和n2写反了。只要试试修改main里的表达式,让它包含减法或者除法就会看出来,因为减法和除法不满足交换律。)
就可以稍微简化(并修正错误)为:
[code="java"]private int doEval(List list) {
Stack stack = new Stack();
String element;
int n1,n2,result;
try {
for(int i = 0; i < list.size();i++) {
element = list.get(i);
if(isOperator(element)) {
n2 = stack.pop();
n1 = stack.pop();
result = doOperate(n1,n2,element);
stack.push(result);
} else {
stack.push(Integer.parseInt(element));
}
}
return stack.pop();
} catch (RuntimeException e) {
throw new IllegalExpressionException(e.getMessage());

}
}[/code]
当然这些都只是细节上的问题而已咯。呵呵 ^ ^

关于我的代码里Scaner的使用要是不太明白的话,手工做词法分析的效果也是一样的。其中
[code="java"]this.scanner.useDelimiter("\s+|(?=[.0-9])(?<![.0-9])|(?![.0-9])(?<=[.0-9])|(?![.0-9])(?<![.0-9])");[/code]
这行的意思是将这个scanner对象的分隔符设置为:
1、空白字符;
2、左边是数字,右边不是数字的位置;
3、右边是数字,左边不是数字的位置;
4、左右两边都不是数字的位置。
这样做可以满足分隔单字符或多字符的数字与单字符的运算符,而不支持多字符运算符的分隔(例如说,如果用"**"来表示幂的话就不支持)。这样的设置足够满足问题中例子的表达式的需要,所以就偷懒这么设置了。需要对此有更完整的支持的话,则需要写更多代码,可能涉及到自己实现一个比较简单的词法分析器。

[quote="Oscar558"]问题补充:
请问,我定义
Stack stack = new Stack()
这个的时候,怎么会出错了? [/quote]
缺少import? import java.util.Stack;
缺少分号? Stack stack = new Stack()[color=red][b];[/b][/color]
变量重复定义?或许前面定义过了同名的变量?把编译错误的提示发出来会更清楚是什么问题。

另外一个可能的问题是你用的JDK太老了。Java是从1.5开始才支持泛型的,在1.5之前无法使用Stack。

说来我之前的代码有点问题……优先级设置不太好。嗯我不应该让+跟-、*跟/在同一个优先级的。
[code="java"]import java.util.Scanner;
import java.util.Stack;

public class SimpleCalculator {
/**
* Evaluate an arithmetic expression, and return the result as a double.
* @param input the expression to evaluate.
* @return the evaluated result.
*/
public double evaluate(String input) {
initialize();

    this.scanner = new Scanner(input);
    this.scanner.useDelimiter("\\s+|(?=[.0-9])(?<![.0-9])|(?![.0-9])(?<=[.0-9])|(?![.0-9])(?<![.0-9])");

    Token currentToken = nextToken();
    Token t = null;
    while (null != currentToken) {
        switch (currentToken.getKind()) {
        case NUMBER:
            // Simply push number tokens onto the evaluation stack.
            this.eval.push(currentToken.getValue());
            break;
        case LPAREN:
            // Simply push left parenthesis tokens onto the operator stack.
            this.ops.push(currentToken);
            break;
        case RPAREN:
            // Until a left parenthesis pops off the operator stack, keep
            // poping operators and execute them.
            // If the stack becomes empty without a matching left parenthesis,
            // the expression must have syntax errors.
            for (t = this.ops.pop();
                 TokenKind.LPAREN != t.getKind();
                 t = this.ops.pop()) {
                if (ops.empty()) throw new Error("Syntax Error: unmatched parenthesis");

                doOperation(t);
            }
            break;
        default:
            // For binary arithmetic operators, keep poping operators whose binding power
            // is less or equal to the current token's and execute them; after that push
            // the current token onto the operator stack.
            if (!ops.empty()) {
                for (t = this.ops.pop();
                     currentToken.getKind().getBindingPower() < t.getKind().getBindingPower();
                     t = this.ops.pop()) {                        
                    doOperation(t);
                    if (this.ops.empty()) {
                        t = null;
                        break;
                    }
                }
            }
            if (null != t) ops.push(t);
            ops.push(currentToken);
            break;
        }

        // reinitialize
        currentToken = nextToken();
    }

    // execute remaining operators on stack
    while (!ops.empty()) {
        t = this.ops.pop();
        doOperation(t);
    }

    // the result is on the top of evaluation stack,
    // pop it off and return the result.
    return this.eval.pop();
}

/*
 * Initialize the evaluation and operator stacks.
 */
private void initialize() {
    if (null == this.eval) this.eval = new Stack<Double>();
    if (null == this.ops) this.ops = new Stack<Token>();
    this.eval.clear();
    this.ops.clear();
}

/*
 * Return the next token from the input expression.
 * The token returned will be associated with its numeric value,
 * if and only if the token is a number.
 */
private Token nextToken() {
    Token t = null;
    if (this.scanner.hasNextDouble()) {
        t = new Token(TokenKind.NUMBER, this.scanner.nextDouble());
    } else if (this.scanner.hasNext()) {
        String s = this.scanner.next("[-+*/()]");
        if ("+".equals(s)) {
            t = new Token(TokenKind.ADD);
        } else if ("-".equals(s)) {
            t = new Token(TokenKind.SUBTRACT);
        } else if ("*".equals(s)) {
            t = new Token(TokenKind.MULTIPLY);
        } else if ("/".equals(s)) {
            t = new Token(TokenKind.DIVIDE);
        } else if ("(".equals(s)) {
            t = new Token(TokenKind.LPAREN);
        } else if (")".equals(s)) {
            t = new Token(TokenKind.RPAREN);
        }
    }
    return t;
}

/*
 * Execute a binary arithmetic operation.
 * Pop the top two values off the evaluation stack, do the operation,
 * and then push the result back onto the evaluation stack.
 */
private void doOperation(Token t) {
    double y = this.eval.pop();
    double x = this.eval.pop();
    double temp = t.getKind().doOperation(x, y);
    this.eval.push(temp);
}

/*
 * Tokenizer for the input expression.
 */
private Scanner scanner;

/*
 * Evaluation stack.
 */
private Stack<Double> eval;

/*
 * Operator stack, for converting infix expression to postfix expression.
 */
private Stack<Token> ops;

public static void main(String[] args) {
    if (args.length < 1) {
        System.err.println("Usage: java SimpleCalculator <expression>");
        System.exit(1);
    }

    SimpleCalculator calc = new SimpleCalculator();
    double result = calc.evaluate(args[0]);
    System.out.println(result);
}

}

enum TokenKind {
// operators
ADD(1) {
public double doOperation(double x, double y) {
return x + y;
}
},
SUBTRACT(2) {
public double doOperation(double x, double y) {
return x - y;
}
},
MULTIPLY(3) {
public double doOperation(double x, double y) {
return x * y;
}
},
DIVIDE(4) {
public double doOperation(double x, double y) {
return x / y;
}
},

// punctuation
LPAREN(0),
RPAREN(0),

// number
NUMBER(0);

TokenKind(int bindingPower) {
    this.bindingPower = bindingPower;
}

public int getBindingPower() {
    return this.bindingPower;
}

public double doOperation(double x, double y) {
    return Double.NaN; // dummy, operation not supported
}

private int bindingPower;

}

class Token {
public Token(TokenKind kind) {
this(kind, Double.NaN);
}

public Token(TokenKind kind, double value) {
    this.kind = kind;
    this.value = value;
}

public TokenKind getKind() {
    return this.kind;
}

public double getValue() {
    return this.value;
}

private TokenKind kind;
private double value;

}[/code]

[quote][code="java"]import java.util.List;

public class test1 {
public static void main(String[] args) {
test1 test11 = new test1();
List list;
}
}[/code][/quote]
这段代码我编译没出问题。实在是不太好理解问题出在什么地方。请问您是用什么编辑器,或者说用什么来编译的?是在Eclipse或者Netbeans之类的IDE里呢,还是直接文本编辑器+命令行编译器?

你确保在jbuild开发工具里指定了JDK是1.5?
在看下你的jbuild版本是否支持1.5~
最后 就是还开发工具试下~ 或者重新装jbuilder。。。

JBuilder只在同学的机上用过很老的版本。不太熟
要是重装开发环境,如果可以的话换个新的开发环境和JDK吧?Eclipse和Netbeans都是免费的,用在Java开发上也很方便。