Как вычислить выражение в префиксной записи

Я пытаюсь оценить список, представляющий выражение в префиксной записи. Вот пример такого списка:

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

Как лучше всего оценить значение списка


person ad126    schedule 08.07.2010    source источник
comment
зачем тогда скобки?   -  person Andrey    schedule 08.07.2010
comment
Если это можно выразить с помощью рекурсии, это можно выразить с помощью стека.   -  person KLee1    schedule 08.07.2010
comment
Нет, это не домашнее задание. Я пытаюсь реализовать технику генетического программирования Карла Сима (karlsims.com/papers). /siggraph91.html), который использует s-выражения Lisp, в python.   -  person ad126    schedule 08.07.2010
comment
Просто любопытно @ad126 - вы сделали это на питоне? Можете ли вы поделиться или указать мне какой-либо исходный код для этой техники генетического программирования? спасибо   -  person zubinmehta    schedule 20.09.2015


Ответы (3)


Было бы проще, если бы вы использовали постфикс вместо префикса. См. обратную польскую нотацию (RPN). Имея выражение в RPN, его легко вычислить, используя только один стек.

Но поскольку вы попросили способ оценить выражения prefix без рекурсии и с использованием стеков (возможно, более простой способ, см. РЕДАКТИРОВАТЬ: ниже), вот один из способов:

Мы можем сделать это, используя два стека.

Один стек (назовем его Evaluation) содержит операторы (например, +, sin и т. д.) и операнды (например, 3,4 и т. д.), а другой стек (назовем его Count) содержит кортеж из числа оставшихся операндов + число операндов, ожидаемых оператором.

Каждый раз, когда вы видите оператор, вы помещаете его в стек Evaluation, а соответствующий кортеж — в стек Count.

Каждый раз, когда вы видите операнд (например, 3,5 и т. д.), вы проверяете верхний кортеж стека Count и уменьшаете количество операндов, оставшихся для просмотра в этом кортеже.

Если количество оставшихся операндов становится равным нулю, вы извлекаете кортеж из стека Count. Затем из стека Evaluation вы выталкиваете необходимое количество операндов (вы знаете это из-за другого значения кортежа), выталкиваете оператор и выполняете операцию, чтобы получить новое значение (или операнд).

Теперь поместите новый операнд обратно в стек вычислений. Этот новый ввод операнда заставляет вас еще раз взглянуть на вершину стека 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

РЕДАКТИРОВАТЬ:

Друг предложил способ сделать это без нескольких стеков:

Начните с конца, идите к первому оператору. Токены справа от этого будут операндами. Оценить и повторить. Кажется намного проще, чем делать это с двумя стеками. Мы можем использовать двусвязный список для представления входных данных, которые мы изменяем во время обработки. При оценке вы удаляете узлы, а затем вставляете результат. Или вы могли бы просто использовать один стек.

person Community    schedule 08.07.2010
comment
Большое спасибо - это именно то, что искал. Из любопытства, будет ли сложно преобразовать префиксную нотацию в постфиксную? - person ad126; 08.07.2010
comment
@ad126: ad126: Это может быть сложно, так как один раз реверс не годится. Вы также должны преобразовать каждый подсписок. Если вам нужно это сделать (т.е. вы не можете избежать префикса), вы можете просто использовать описанный выше алгоритм с одним проходом вместо того, чтобы пытаться преобразовать в постфикс, а затем использовать оценщик RPN. - person ; 08.07.2010
comment
Слово, придурок. Спасибо за вашу помощь. - person ad126; 08.07.2010
comment
@ad126: друг предложил более простой способ, который вам может показаться легче реализовать (хотя я не пытался проверить правильность). Я отредактировал ответ, включив в него это (и, возможно, это даже то, что хотел сказать Пол). - person ; 09.07.2010

KISS, оценивайте в обратном порядке как постфиксное выражение.

person paul    schedule 08.07.2010
comment
Да, но вам придется изменить порядок операндов. В противном случае [/, 1, 2] будет оцениваться как 2 вместо 1/2. - person Ohad Schneider; 09.12.2012

Как я вижу, у вас есть два варианта. Либо идите слева направо, либо справа налево (как Павел предложил выше). Оба метода демонстрируются в приведенном ниже коде.

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"));
}
person Ohad Schneider    schedule 08.12.2012