【问题标题】:How to evaluate an expression in prefix notation如何以前缀表示法评估表达式
【发布时间】:2010-07-08 16:13:25
【问题描述】:

我正在尝试评估一个以前缀表示法表示表达式的列表。以下是此类列表的示例:

[+, [sin, 3], [- 10 5]]

评估列表价值的最佳方法是什么

【问题讨论】:

  • 那为什么需要括号呢?
  • 如果可以用递归表示,就可以用栈表示。
  • 不,这不是家庭作业——我正在尝试在 python 中实现 Karl Sim 的遗传编程技术 (karlsims.com/papers/siggraph91.html),它使用 Lisp s 表达式。
  • 只是好奇@ad126——你在 python 中做过这个吗?你能分享或指出这种遗传编程技术的任何源代码吗?谢谢

标签: expression-trees evaluation s-expression


【解决方案1】:

如果你使用后缀而不是前缀会更简单。见Reverse Polish Notation (RPN)。给定 RPN 中的表达式,只需使用一个堆栈即可轻松评估它。

但是由于您要求一种方法来评估 prefix 表达式而无需递归和使用堆栈(可能更简单的方法,请参阅下面的编辑:),这是一种方法:

我们可以使用两个堆栈来做到这一点。

一个堆栈(称为评估)保存运算符(如 +、sin 等)和操作数(如 3,4 等),另一个堆栈(称为计数)保存剩余操作数数量的元组+ 运算符期望的操作数。

任何时候你看到一个操作符,你就将这个操作符压入求值堆栈,并将相应的元组压入计数堆栈。

每当您看到一个操作数(如 3,5 等)时,您都会检查 Count 堆栈的顶部元组并减少该元组中剩余的操作数数量。

如果要查看的操作数数量变为零,则将元组从 Count 堆栈中弹出。然后从评估堆栈中弹出所需的操作数数量(您知道这是因为元组的其他值),弹出运算符并执行操作以获得新值(或操作数)。

现在将新操作数推回评估堆栈。这个新的操作数推送让您再次查看 Count 堆栈的顶部,然后您执行与我们刚才所做的相同的操作(减少看到的操作数,与零进行比较等)。

如果操作数计数不为零,则继续输入中的下一个标记。

例如说你必须计算 + 3 + 4 / 20 4

堆栈看起来像(左边是堆栈的顶部)

Count                  Evaluation                   Input
                                                   + 3 + 4 / 20 4

(2,2)                   +                            3 + 4 / 20 4

(2,1)                   3 +                            + 4 / 20 4

(2,2) (2,1)             + 3 +                            4 / 20 4

(2,1) (2,1)             4 + 3 +                            / 20 4

(2,2) (2,1) (2,1)       / 4 + 3 +                            20 4

(2,1) (2,1) (2,1)       20 / 4 + 3 +                            4

(2,0) (2,1) (2,1)       4 8 / 4 + 3 +                   

Since it has become zero, you pop off two operands, the operator / 
and evaluate and push back 5. You pop off (2,0) from the Count stack.

(2,1) (2,1)                5 4 + 3 +

Pushing back you decrement the current Count stack top.

(2,0) (2,1)               5 4 + 3 + 

Since it has become zero, you pop off 5,4 and + and evaluate and push back 9. 
Also pop off (2,0) from the count stack.

(2,0)                          9 3 + 

                               12

编辑:

一位朋友建议了一种无需多个堆栈的方法:

从头开始,到第一个操作员。右侧的标记将是操作数。评估并重做。似乎比使用两个堆栈简单得多。我们可以使用双向链表来表示我们在处理过程中更改的输入。评估时,删除节点,然后插入结果。或者你可以只使用一个堆栈。

【讨论】:

  • 非常感谢 - 这正是我们正在寻找的。出于好奇,从前缀表示法转换为后缀表示法会很困难吗?
  • @ad126:这可能会变得很棘手,因为仅反转一次是行不通的。您也必须转换每个子列表。如果你必须这样做(即你不能避免前缀),你不妨只使用上面的一次通过算法,而不是尝试转换为后缀,然后使用 RPN 评估器。
  • 字,白痴。感谢您的帮助。
  • @ad126:一位朋友提出了一种更简单的方法,您可能会发现它更容易实现(尽管我没有尝试验证正确性)。我已经编辑了答案以包含它(实际上甚至可能是保罗想要说的)。
【解决方案2】:

KISS,作为后缀表达式反向计算。

【讨论】:

  • 是的,但是您必须颠倒操作数的顺序。否则 [/, 1, 2] 将被评估为 2 而不是 1/2。
【解决方案3】:

在我看来,您有两种选择。要么从左到右,要么从右到左(如上面保罗建议的那样)。这两种方法都在下面的代码中进行了演示。

public static class Evaluator
{
   public static double EvaluatePrefix(string expr)
   {
       if (expr == null) throw new ArgumentNullException("expr");

       var symbols = expr.Split(',');
       var stack = new Stack<Symbol>();

       foreach (var symbol in symbols)
       {
           double operand;
           if (!double.TryParse(symbol, out operand)) //encountered an operator
           {
               stack.Push(new Operator(symbol));
               continue;
           }

           //encountered an operand
           if (stack.Count == 0) throw new ArgumentException("Invalid expression");

           double right = operand;
           var leftOperand = stack.Peek() as Operand;
           while (leftOperand != null)
           {
               stack.Pop(); //pop left operand that we just peeked
               if (stack.Count == 0) throw new ArgumentException("Invalid expression");
               double result = Calculate(leftOperand.Value, right, ((Operator)stack.Pop()).OperatorChar);

               right = result;
               leftOperand = (stack.Count == 0) ? null : stack.Peek() as Operand;
           }
           stack.Push(new Operand(right));
       }

       if (stack.Count != 1) throw new ArgumentException("Invalid expression");
       var operandSymbol = stack.Pop() as Operand;
       if (operandSymbol == null) throw new ArgumentException("Invalid expression");
       return operandSymbol.Value;
   }

   public static double EvaluatePrefixAlternative(string expr)
   {
       if (expr == null) throw new ArgumentNullException("expr");

       double d;
       var stack = new Stack<Symbol>(
           expr.Split(',').Select(s => double.TryParse(s, out d) ? (Symbol) new Operand(d) : new Operator(s)));

       var operands = new Stack<double>();
       while (stack.Count > 0)
       {
           var symbol = stack.Pop();
           var operand = symbol as Operand;
           if (operand != null)
           {
               operands.Push(operand.Value);
           }
           else
           {
               if (operands.Count < 2) throw new ArgumentNullException("expr");
               operands.Push(Calculate(operands.Pop(), operands.Pop(), ((Operator) symbol).OperatorChar));
           } 
       }

       if (operands.Count != 1) throw new ArgumentNullException("expr");
       return operands.Pop();
   }

   private static double Calculate(double left, double right, char op)
   {
       switch (op)
       {
           case '*':
               return (left * right);
           case '+':
               return (left + right);
           case '-':
               return (left - right);
           case '/':
               return (left / right); //May divide by zero !
           default:
               throw new ArgumentException(string.Format("Unrecognized operand {0}", op), "op");
       }
   }

   abstract class Symbol
   {
   }

   private class Operand : Symbol
   {
       public double Value { get; private set; }

       public Operand(double value)
       {
           Value = value;
       }
   }

   private class Operator : Symbol
   {
       public char OperatorChar { get; private set; }

       public Operator(string symbol)
       {
           if (symbol.Trim().Length != 1) throw new ArgumentException("Invalid expression");
           OperatorChar = symbol[0];
       }
   }
}

一些测试:

[TestMethod]
public void TestPrefixEvaluation()
{
  Assert.AreEqual(5, Evaluator.EvaluatePrefix("-,*,/,15,-,7,+,1,1,3,+,2,+,1,1"));
  Assert.AreEqual(4, Evaluator.EvaluatePrefix("/,-,*,2,5,*,1,2,-,11,9"));
  Assert.AreEqual(5, Evaluator.EvaluatePrefixAlternative("-,*,/,15,-,7,+,1,1,3,+,2,+,1,1"));
  Assert.AreEqual(4, Evaluator.EvaluatePrefixAlternative("/,-,*,2,5,*,1,2,-,11,9"));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-13
    相关资源
    最近更新 更多