以字符串格式对代数表达式进行标记

时间:2013-06-17 21:58:09

标签: java

我试图取一个表示完整代数表达式的字符串,例如x = 15 * 6/3这是一个字符串,并将其标记为其各个组件。所以第一个是x,然后是=,然后是15,然后是*,6,/最后是3。

我遇到的问题实际上是解析字符串并查看单个字符。没有大量的if语句,我想不出办法做到这一点。当然,必须有一个更好的方法,专门定义每个案例并进行测试。

4 个答案:

答案 0 :(得分:3)

对于每种类型的令牌,您都需要弄清楚如何识别:

  • 当您开始阅读特定令牌时
  • 如果您继续阅读相同的令牌,或者您已经开始使用其他令牌

我们举个例子:x=15*6/3。让我们假设你不能依赖每个令牌之间有空格的事实。在这种情况下,它是微不足道的:当你到达一个空间时,你的新令牌就开始了。

您可以将字符类型细分为字母,数字和符号。让我们调用令牌类型Variable,Operator和Number。

字母表示变量令牌已启动。它一直持续到你读了一封非字母。

符号表示操作员令牌的开头。我只看到单个符号,但您可以将符号组对应不同的运算符标记。

数字表示数字标记的开头。 (我们现在假设整数。)数字标记继续,直到您读取非数字。

基本上,这就是简单的符号解析器的工作原理。现在,如果你添加负数(其中' - '符号可以有多个含义),或括号或函数名称(如sin(x)),那么事情变得更复杂,但它相当于同一组规则,现在只有更多的选择。

答案 1 :(得分:2)

这是来自我的早期表达式评估程序,它采用与您类似的中缀表达式并将其转换为后缀以进行评估。有一些方法可以帮助解析器,但我认为它们非常自我记录。我使用符号表来检查令牌。它还允许用户定义的符号和嵌套的赋值以及您可能不需要/不想要的其他内容。但它显示了我如何处理你的问题而不使用像正则表达这样可以极大地简化这项任务的细节。此外,所有显示的都是我自己的实现 - 堆栈和队列 - 一切。因此,如果有任何看起来异常(不像Java imps)那是因为它是。

这部分代码非常重要,不是为了回答您的直接问题,而是为了展示确定您正在处理的令牌类型的必要工作。在我的例子中,我有三种不同类型的运算符和两种不同类型的操作数。根据我选择强制执行的已知规则或规则(适当时),很容易知道什么时候是某个数字(以数字开头),变量/用户符号/数学函数(以字母开头)或数学运算符(是:/,*, - ,+)。请注意,只需要查看第一个char即可知道正确的提取规则。从您的示例中,如果您的所有情况都如此简单,则只需处理两种类型,运算符或操作数。尽管如此,相同的逻辑仍然适用。

protected Queue<Token> inToPostParse(String exp) {
    // local vars
    inputExp = exp;
    offset = 0;
    strLength = exp.length();
    String tempHolder = "";
    char c;

    // the program runs in a loop so make sure you're dealing
    // with an empty queue
    q1.reset();

    for (int i = offset; tempHolder != null && i < strLength; ++i) {
        c = exp.charAt(i); 

        // Spaces are useless so skip them
        if (c == ' ') { continue; }

        // If c is a letter
        if ((c >= 'A' && c <= 'Z')
                || (c >= 'a' && c <= 'z')) {

            // Here we know it must be a user symbol possibly undefined
            // at this point or an function like SIN, ABS, etc
            // We extract, based on obvious rules, the op
            tempHolder = extractPhrase(i); // Used to be append sequence
            if (ut.isTrigOp(tempHolder) || ut.isAdditionalOp(tempHolder)) {
                s1.push(new Operator(tempHolder, "Function"));
            } else {
                // If not some math function it is a user defined symbol
                q1.insert(new Token(tempHolder, "User"));
            }
            i += tempHolder.length() - 1;
            tempHolder = "";

        // if c begins with a number
        } else if (c >= '0' && c <= '9') {
            try {
                // Here we know that it must be a number
                // so we extract until we reach a non number
                tempHolder = extractNumber(i);
                q1.insert(new Token(tempHolder, "Number"));
                i += tempHolder.length() - 1;
                tempHolder = "";
            }
            catch (NumberFormatException nfe) {
                return null;
            }

        // if c is in the math symbol table
        } else if (ut.isMathOp(String.valueOf(c))) {
            String C = String.valueOf(c);
            try {
                // This is where the magic happens
                // Here we determine the "intersection" of the 
                // current C and the top of the stack
                // Based on the intersection we take action
                // i.e., in math do you want to * or + first?
                // Depending on the state you may have to move
                // some tokens to the queue before pushing onto the stack 
                takeParseAction(C, ut.findIntersection
                            (C, s1.showTop().getSymbol()));
            }
            catch (NullPointerException npe) {
                s1(C);
            }
        // it must be an invalid expression
        } else {
            return null;
        }
    }
    u2();
    s1.reset();
    return q1;
}

基本上我有一个堆栈(s1)和一个队列(q1)。所有变量或数字都进入队列。任何运算符都是trig,math,parens等。进入堆栈。如果要将当前令牌放在堆栈上,则必须检查状态(顶部)以确定要采取的解析操作(即,基于数学优先级做什么)。对不起,如果这看起来像无用的信息。我想如果你正在解析一个数学表达式,那是因为在某些时候你打算对它进行评估。恕我直言,postfix是最简单的,所以我,无论输入格式如何,将其更改为发布并使用一种方法进行评估。如果你的O不同 - 做你喜欢的事。

修改:实施方案
您可能最感兴趣的提取短语和数字方法如下:

protected String extractPhrase(int it) {
    String phrase = new String();
    char c;
    for ( ; it < inputExp.length(); ++it) {
        c = inputExp.charAt(it);
        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
                        || (c >= '0' && c <= '9')) {
                    phrase += String.valueOf(c);
        } else {
            break;
        }
    }
    return phrase;
}

protected String extractNumber(int it) throws NumberFormatException {
    String number = new String();
    int decimals = 0;
    char c;
    for ( ; it < strLength; ++it) {
        c = inputExp.charAt(it);
        if (c >= '0' && c <= '9') {
            number += String.valueOf(c);
        } else if (c == '.') {
            ++decimals;
            if (decimals < 2) {
                number += ".";
            } else {
                throw new NumberFormatException();
            }               
        } else {
            break;
        }
    }
    return number;
}

记住 - 当他们进入这些方法时,我已经能够推断它是什么类型。这可以让你避免看似无穷无尽的if-else-else链。

答案 2 :(得分:2)

  1. 为每个可能的元素创建正则表达式:整数,变量,运算符,括号。
  2. 使用|正则表达式运算符将它们组合成一个带有捕获组的正则表达式,以识别哪个匹配。
  3. 在循环中匹配剩余字符串的头部并将匹配的部分作为标记中断。令牌的类型取决于匹配的子表达式,如2中所述。
  4. 使用词法分析器库,例如antlr或javacc中的词法分析器

答案 3 :(得分:1)

组件是否总是按照您的问题中的空格字符分隔?如果是这样,请使用algebricExpression.split(" ")获取String[]个组件。

如果不能假设这样的限制,可能的解决方案是迭代输入,并switch当前索引的Character.getType(),类似于:

ArrayList<String> getExpressionComponents(String exp) {
    ArrayList<String> components = new ArrayList<String>();
    String current = "";
    int currentSequenceType = Character.UNASSIGNED;


    for (int i = 0 ; i < exp.length() ; i++) {
        if (currentSequenceType != Character.getType(exp.charAt(i))) {
            if (current.length() > 0) components.add(current);
            current = "";
            currentSequenceType = Character.getType(exp.charAt(i));
        }
        switch (Character.getType(exp.charAt(i))) {
            case Character.DECIMAL_DIGIT_NUMBER: 
            case Character.MATH_SYMBOL: 
            case Character.START_PUNCTUATION: 
            case Character.END_PUNCTUATION:
            case Character.LOWERCASE_LETTER:
            case Character.UPPERCASE_LETTER:
            // add other required types
                current = current.concat(new String(new char[] {exp.charAt(i)}));
                currentSequenceType = Character.getType(exp.charAt(i));
                break;
            default: 
                current = "";
                currentSequenceType = Character.UNASSIGNED;
                break;
        }
    }
    return components;
}

您可以轻松更改案例以满足其他要求,例如将非数字字符拆分为单独的组件等。