如何构建数学表达式的解析树?

11

我正在学习如何编写标记器(tokenizers)、解析器(parsers),并以此作为练习在 JavaScript 中编写计算器。

我使用了一种语法树(prase tree)的方法(我希望我用对了这个术语),来构建我的计算器。我基于运算符优先级建立了一个令牌(token)的树。

例如,给定表达式a*b+c*(d*(g-f)),正确的树结构应该是:

         +
        / \
       *   \
      / \   \
     a   b   \
              *
             / \
            c   *
               / \
              d   -
                 / \
                g   f

一旦我有了这棵树,我可以沿着它向下遍历,并递归地在每个根节点上应用左右节点的操作来找到表达式的值。

然而,最大的问题实际上是构建这棵树。我就是想不出该如何正确地构建它。我不能仅仅在运算符 +-/* 上分裂并从左右部分创建树,因为存在优先级问题。

到目前为止,我所做的是对表达式进行标记化。所以给定 a*b+c*(d*(g-f)),我最终得到一个标记数组:

[a, Operator*, b, Operator+, c, Operator*, OpenParen, d, Operator*, OpenParen, g, Operator-, f, CloseParen, CloseParen]

但是我无法想出下一步该如何从这个令牌数组转换为一棵我可以遍历并找出该值的树。有人可以帮我想出如何做吗?


2
请查看 Shunting-yard 算法。 - Coder404
@bmargulies,您能解释一下应该关注什么吗?我不明白什么是解析器生成器...有很多不同的页面涉及到这个术语。 - bodacydo
查找yacc或antlr。这些工具可以解析语言语法并生成代码;您只需填写代码以创建各个节点即可。 - bmargulies
这可能会有用:1052470/javascript-parser-for-simple-expression - Ryan Vincent
解析器生成器的使用是因为手写好的解析代码可能会很困难和繁琐。解析器生成器接受正式的语言规范并输出代码。它们非常适用于计算机语言的解析器。数学方程是一个特殊情况,它们的结构与大多数语言不同,并且使用yacc或bison很容易出错。通常,这些方程的解析器是使用https://en.wikipedia.org/wiki/Shunting_yard_algorithm手写编码的。顺便说一下,我已经在http://singsurf.org/djep/GWTJep.php上编写了自己的数学解析库。 - Salix alba
显示剩余2条评论
4个回答

7

嗨,朋友,我不知道如何让这个看起来更漂亮。

我用C语言写了一个类似的程序,但我的树是倒置的,意味着新的运算符成为了根节点。

C代码中的计算器解析树,请参阅readme文件

例如:输入2 + 3 - 4

从空节点开始

{}

规则1:当你读到一个数字时,在当前节点的左侧或右侧添加一个子节点,其中一个为空

      {}
     /
   {2}

规则2:当你读到一个运算符时,必须从当前节点 {2} 开始向上爬到一个空节点,当找到一个空节点时,将其值更改为 +,如果没有空节点,则必须创建一个新的节点并使其成为树的根节点。

      {+}
     /
   {2}

如果你遇到另一个数字,请按照规则1操作,现在我们在{+}位置,找到一侧是空的(这次在右边)。

      {+}
     /   \
   {2}   {3}

现在有一个新的操作符“-”,但是因为{3}的父节点已经满了,你必须创建一个新节点并将其设为根节点。
        {-}
        /
      {+}
     /   \
   {2}   {3}

看,又是一个数字。因为我们当前指向根节点,让我们找到一个空的{-}子节点(提示:在右边)。

         {-}
        /   \
      {+}   {4}
     /   \
   {2}   {3}

一旦构建了一棵树,请查看函数getresult()以计算所有内容。
您可能想知道括号化如何工作。我是这样做的:
每次遇到“(”时,我都会创建全新的树,并将输入的其余部分构建到该树中。如果我读取另一个“(”,我将创建另一个树并继续使用新树构建。
读取输入后,我将所有树的根连接在一起,以形成最终的树。查看代码和自述文件,我必须画图来解释一切。
希望这也能帮助未来的读者。

看起来 GitHub 的链接已经失效了,也许你删除或重命名了仓库? - timlg07
我认为现在是 https://github.com/scarface382/math-parser。 - asaklein

0

我经常看到这个问题,所以我写了这篇文章。这肯定会让你朝着正确的方向前进,但要小心,这是一个自顶向下的解析器,因此它可能无法按照您预期的优先级解释表达式。

class Program
{
    static void Main()
    {
        Console.WriteLine(SimpleExpressionParser.Parse("(10+30*2)/20").ToString());
        Console.ReadLine();
        //
        // ouput: ((10+30)*2)/20
    }

    public static class SimpleExpressionParser
    {
        public static SimpleExpression Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException("str");
            }

            int index = 0;

            return InternalParse(str, ref index, 0);
        }

        private static SimpleExpression InternalParse(string str, ref int index, int level)
        {
            State state = State.ExpectLeft;
            SimpleExpression _expression = new SimpleExpression();

            int startIndex = index;
            int length = str.Length;

            while (index < length)
            {
                char chr = str[index];

                if (chr == ')' && level != 0)
                {
                    break;
                }

                switch (state)
                {
                    case State.ExpectLeft:
                    case State.ExpectRight:
                        {
                            SimpleExpression expression = null;

                            if (Char.IsDigit(chr))
                            {
                                int findRep = FindRep(Char.IsDigit, str, index + 1, length - 1);

                                expression = new SimpleExpression(int.Parse(str.Substring(index, findRep + 1)));
                                index += findRep;
                            }
                            else if (chr == '(')
                            {
                                index++;
                                expression = InternalParse(str, ref index, level + 1);
                            }

                            if (expression == null)
                            {
                                throw new Exception(String.Format("Expression expected at index {0}", index));
                            }

                            if (state == State.ExpectLeft)
                            {
                                _expression.Left = expression;
                                state = State.ExpectOperator;
                            }
                            else
                            {
                                _expression.Right = expression;
                                state = State.ExpectFarOperator;
                            }
                        }
                        break;

                    case State.ExpectOperator:
                    case State.ExpectFarOperator:
                        {
                            SimpleExpressionOperator op;

                            switch (chr)
                            {
                                case '+': op = SimpleExpressionOperator.Add; break;
                                case '-': op = SimpleExpressionOperator.Subtract; break;
                                case '*': op = SimpleExpressionOperator.Multiply; break;
                                case '/': op = SimpleExpressionOperator.Divide; break;

                                default:
                                    throw new Exception(String.Format("Invalid operator encountered at index {0}", index));
                            }

                            if (state == State.ExpectOperator)
                            {
                                _expression.Operator = op;
                                state = State.ExpectRight;
                            }
                            else
                            {
                                index++;

                                return new SimpleExpression(op, _expression, InternalParse(str, ref index, level));
                            }
                        }
                        break;
                }

                index++;
            }

            if (state == State.ExpectLeft || state == State.ExpectRight)
            {
                throw new Exception("Could not complete expression");
            }

            return _expression;
        }

        private static int FindRep(Func<char, bool> validator, string str, int beginPos, int endPos)
        {
            int pos;

            for (pos = beginPos; pos <= endPos; pos++)
            {
                if (!validator.Invoke(str[pos]))
                {
                    break;
                }
            }

            return pos - beginPos;
        }

        private enum State
        {
            ExpectLeft,
            ExpectRight,
            ExpectOperator,
            ExpectFarOperator
        }
    }

    public enum SimpleExpressionOperator
    {
        Add,
        Subtract,
        Multiply,
        Divide
    }

    public class SimpleExpression
    {
        private static Dictionary<SimpleExpressionOperator, char> opEquivs = new Dictionary<SimpleExpressionOperator, char>()
        {
            { SimpleExpressionOperator.Add, '+' },
            { SimpleExpressionOperator.Subtract, '-' },
            { SimpleExpressionOperator.Multiply, '*' },
            { SimpleExpressionOperator.Divide, '/' }
        };

        public SimpleExpression() { }

        public SimpleExpression(int literal)
        {
            Literal = literal;
            IsLiteral = true;
        }

        public SimpleExpression(SimpleExpressionOperator op, SimpleExpression left, SimpleExpression right)
        {
            Operator = op;
            Left = left;
            Right = right;
        }

        public bool IsLiteral
        {
            get;
            set;
        }

        public int Literal
        {
            get;
            set;
        }

        public SimpleExpressionOperator Operator
        {
            get;
            set;
        }

        public SimpleExpression Left
        {
            get;
            set;
        }

        public SimpleExpression Right
        {
            get;
            set;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            AppendExpression(sb, this, 0);

            return sb.ToString();
        }

        private static void AppendExpression(StringBuilder sb, SimpleExpression expression, int level)
        {
            bool enclose = (level != 0 && !expression.IsLiteral && expression.Right != null);

            if (enclose)
            {
                sb.Append('(');
            }

            if (expression.IsLiteral)
            {
                sb.Append(expression.Literal);
            }
            else
            {
                if (expression.Left == null)
                {
                    throw new Exception("Invalid expression encountered");
                }

                AppendExpression(sb, expression.Left, level + 1);

                if (expression.Right != null)
                {
                    sb.Append(opEquivs[expression.Operator]);
                    AppendExpression(sb, expression.Right, level + 1);
                }
            }

            if (enclose)
            {
                sb.Append(')');
            }
        }
    }
}

问题涉及JavaScript,而不是C#。 - gdbdable

0

查看https://github.com/carlos-chaguendo/arboles-binarios 该算法将代数表达式转换为二叉树。将表达式转换为后缀形式,然后评估并绘制树


测试:2+3*4+2^3

输出:

    22 =
        └── +
            ├── +
            │   ├── 2
            │   └── *
            │       ├── 3
            │       └── 4
            └── ^
                ├── 2
                └── 3

-1
你可能会对math.js感兴趣,它是一个JavaScript数学库,包含高级表达式解析器(请参见文档示例)。你可以轻松地解析表达式,例如:
var node = math.parse('a*b+c*(d*(g-f))');

该函数返回一个节点树(可编译和评估)。

您可以在此处查看解析器代码:https://github.com/josdejong/mathjs/blob/master/src/expression/parse.js

您可以在此处找到一些简单表达式解析器的示例(用于C++和Java):http://www.speqmath.com/tutorials/,这些示例对于学习表达式解析器的基础知识非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接