разбор префиксной нотации в python

Сразу же - нет, это НЕ домашнее задание.

Я хотел бы написать парсер префиксной нотации в python (для сумм в настоящее время)... например

если задано: + 2 2, будет возвращено: 4

идеи?


person tekknolagi    schedule 15.03.2011    source источник
comment
Есть ли скобки во входных данных?   -  person Cameron    schedule 15.03.2011
comment
Написание интерпретатора FORTH или PostScript... или эмулятора калькулятора HP... :-)   -  person kindall    schedule 15.03.2011
comment
я пишу простой математический язык. пример ввода выглядит следующим образом: *0%5*1@2%7, где *0 — это примитив «набор», %5 указывает установить 5-е значение в массиве, *1 — это примитив для «добавить», а @2 указывает ему второе место в массиве. я только изучаю питон, @Marcelo. чиллакс.   -  person tekknolagi    schedule 15.03.2011
comment
@kindall: оба они постфиксные.   -  person Marcelo Cantos    schedule 15.03.2011
comment
Yyyyyyyyyyyyeah, я имел в виду LISP. :-)   -  person kindall    schedule 15.03.2011
comment
наконец-то кто-то прав!   -  person tekknolagi    schedule 15.03.2011
comment
@tekknolagi: Можно быть одновременно циничным и совершенно расслабленным в отношении жизни.   -  person Marcelo Cantos    schedule 15.03.2011
comment
@Марсело, я знаю, как я живу   -  person tekknolagi    schedule 15.03.2011
comment
@Cameron - без скобок ... мой ввод будет * 1% 2% 3 сделать - 2 3   -  person tekknolagi    schedule 15.03.2011


Ответы (8)


def prefix(input):
  op, num1, num2 = input.split(" ")
  num1 = int(num1)
  num2 = int(num2)
  if op == "+":
    return num1 + num2
  elif op == "*":
    return num1 * num2
  else:
    # handle invalid op
    return 0

print prefix("+ 2 2")

печатает 4, также включает оператор умножения, чтобы показать, как его расширить.

person Mike Lewis    schedule 15.03.2011
comment
На самом деле это не оценщик, и он не работает для произвольных префиксных выражений (попробуйте + + 2 2 4). Он работает только для самых простых форм выражений (т. е. имеющих только один оператор и два числовых аргумента). - person MAK; 15.03.2011
comment
Я не претендую на то, чтобы написать полноценный оценщик, я просто делал для него что-то, с чего можно начать. - person Mike Lewis; 15.03.2011
comment
Это также не проверяет все выражение. Если 2-й и 3-й разделяемые токены не являются числами, они вызовут исключение. - person pokstad; 15.03.2011

Префиксную нотацию можно очень легко вычислить рекурсивно. В основном вы видите первый токен, и если это «+», вы оцениваете последующие подвыражения, чтобы получить значения, которые нужно добавить, и просто суммируете их. Если это число, вы просто возвращаете число.

В следующем коде предполагается, что ввод правильно отформатирован и является допустимым выражением.

#! /usr/bin/env python
from collections import deque
def parse(tokens):
    token=tokens.popleft()
    if token=='+':
            return parse(tokens)+parse(tokens)
    elif token=='-':
            return parse(tokens)-parse(tokens)
    elif token=='*':
            return parse(tokens)*parse(tokens)
    elif token=='/':
            return parse(tokens)/parse(tokens)
    else:
            # must be just a number
            return int(token)


if __name__=='__main__':
        expression="+ 2 2"
        print parse(deque(expression.split()))
person MAK    schedule 15.03.2011
comment
предположим, что у него есть вложенные выражения, но в моем формате кода... *1*2%3%1%5 будет оцениваться как (префикс): (+ (- 3 1) 5)... как мне его разобрать? - person tekknolagi; 15.03.2011
comment
@teknolagi: зачем вам скобки в префиксном выражении? Порядок оценки однозначен. Этот код должен нормально работать с + + 3 1 5. Сейчас он поддерживает только +, но вы можете легко добавить кейсы для - и других операторов. - person MAK; 15.03.2011
comment
@teknolagi: отредактировал его так, чтобы он работал и с вычитанием, умножением и делением. Теперь ваш тестовый пример должен работать. - person MAK; 15.03.2011

Вот что я наработал. Он хранит стек операторов. Когда он получает достаточное количество чисел, он выталкивает оператор и вычисляет подвыражение.

# Bring in the system module to get command line parameters
import sys

# This function takes in some binary operator, which is just an arbitrary
#  string, and two values.  It looks up the method associated with the
#  operator, passes the two values into that method, and returns the
#  method's result.
def eval_expression(operator, value_one, value_two):
  if operator == "+":
    return value_one + value_two
  elif operator == "-":
    return value_one - value_two
  elif operator == "*":
    return value_one * value_two
  elif operator == "/":
    return value_one / value_two
  # Add new operators here.  For example a modulus operator could be
  #  created as follows:
  #       elif operator == "mod":
  #         return value_one % value_two
  else:
    raise Exception(operator, "Unknown operator")

# This function takes in a string representing a prefix equation to
#  evaluate and returns the result.  The equation's values and
#  operators are space delimited.
def calculate( equation ):
  # Gather the equation tokens
  tokens = equation.split( " " )

  # Initialize the evaluation stack.  This will contain the operators
  #  with index 0 always containing the next operator to utilize.  As
  #  values become available an operator will be removed and
  #  eval_expression called to calculate the result.
  eval_stack = [ ]
  total = None

  # Process all the equation tokens
  for token in tokens:
    if token.isdigit():
      # Save the first value.  Subsequent values trigger the evaluation
      #  of the next operator applied to the total and next values
      token = int(token)
      if total is None:
        total = token
      else:
        total = eval_expression(eval_stack.pop(0), total, token)

    else:
      # Save the new operator to the evaluation stack
      eval_stack.insert(0, token)

  # Done!  Provide the equation's value
  return total

# If running standalone pass the first command line parameter as
#  an expression and print the result.  Example:
#       python prefix.py "+ / 6 2 3 - 6"
if __name__ == '__main__':
  print calculate( sys.argv[1] )

Мне также нравится рекурсивная функция MAK.

person Avilo    schedule 15.03.2011
comment
Могу ли я получить объяснение того, что делает этот код? (новичок, и пошаговое руководство было бы потрясающим) - person tekknolagi; 16.03.2011
comment
Найдите рекурсию, этот код рекурсивно оценивает ваши математические выражения. Он будет постоянно вызывать одну и ту же функцию снова и снова, пока не достигнет базового условия, которое заставит ее вернуть фактическое значение. Очень элегантное решение. - person pokstad; 16.03.2011
comment
Добавил несколько комментариев. Надеюсь, они помогут вам. Это не использует рекурсию. Это просто итеративная функция, использующая массив в качестве очереди LIFO для хранения операндов и устранения необходимости рекурсии. - person Avilo; 16.03.2011

Переверните токены и используйте стековую машину следующим образом:

def prefix_eval(tokens):
    stack = []
    for t in reversed(tokens):
        if   t == '+': stack[-2:] = [stack[-1] + stack[-2]]
        elif t == '-': stack[-2:] = [stack[-1] - stack[-2]]
        elif t == '*': stack[-2:] = [stack[-1] * stack[-2]]
        elif t == '/': stack[-2:] = [stack[-1] / stack[-2]]
        else: stack.append(t)
    assert len(stack) == 1, 'Malformed expression'
    return stack[0]

>>> prefix_eval(['+', 2, 2])
4
>>> prefix_eval(['-', '*', 3, 7, '/', 20, 4])
16

Обратите внимание, что stack[-1] и stack[-2] меняются местами по отношению к обычной стековой машине. Это сделано для того, чтобы учесть тот факт, что на самом деле это префиксная нотация в обратном порядке.

Я должен объяснить несколько идиом Python, которые я использовал:

  1. stack = []: В Python нет встроенного объекта стека, но списки легко создаются для той же цели.
  2. stack[-1] и stack[-2]: Python поддерживает отрицательные индексы. stack[-2] относится к предпоследнему элементу списка.
  3. stack[-2:] = ...: This assignment combines two idioms in addition to negative indexing:
    1. Slicing: A[x:y] refers to all the elements of A from x to y, including x but excluding y (e.g., A[3:5] refers to elements 3 and 4). An omitted number implies either the start or the end of the list. Therefore, stack[-2:] refers to every element from the second-last to the end of the list, i.e., the last two elements.
    2. Присвоение фрагмента: Python позволяет вам присваивать фрагменту, что приводит к объединению нового списка вместо элементов, на которые ссылается фрагмент.

Собрав все вместе, stack[-2:] = [stack[-1] + stack[-2]] складывает два последних элемента стека, создает из суммы одноэлементный список и присваивает этот список срезу, содержащему два числа. Чистый эффект состоит в том, чтобы заменить два самых верхних числа в стеке их суммой.

Если вы хотите начать со строки, вам поможет простой интерфейсный синтаксический анализатор:

def string_eval(expr):
    import re
    return prefix_eval([t if t in '+-*/' else int(t)
                        for t in re.split(r'\s+', expr)])

>>> string_eval('/ 15 - 6 3')
5
person Marcelo Cantos    schedule 15.03.2011
comment
? я еще новичок... что такое стек? и жетоны? - person tekknolagi; 15.03.2011
comment
@tekknolagi: токены — это небольшие фрагменты исходного текста, которые синтаксический анализатор и компилятор рассматривают как неделимые. Например, в коде Python stack[-2:] = [stack[-1] + stack[-2]] маркерами являются: stack, [, -, 2, :, ], =, [, stack, [, -, 1, ], +, stack, [, -, 2, ] и ]. Обратите внимание, что пробелы в исходном выражении не отображаются, так как они просто служат для разграничения токенов (и в данном случае фактически избыточны). Некоторые языки обрабатывают -2 как одиночный токен, но чаще он анализируется как оператор отрицания, за которым следует неотрицательное целое число. - person Marcelo Cantos; 15.03.2011

Вот пример с лямбда-функциями

ops = {
  "+": (lambda a, b: a + b),
  "-": (lambda a, b: a - b),
  "*": (lambda a, b: a * b),
  "/": (lambda a, b: a / b)
}

def eval(expression):
  tokens = expression.split()
  stack = []

  for token in tokens:
    if token in ops:
      arg2 = stack.pop()
      arg1 = stack.pop()
      result = ops[token](arg1, arg2)
      stack.append(result)
    else:
      stack.append(int(token))

  return stack.pop()   
person Vaghinak    schedule 26.01.2019

регулярное выражение, которое есть:

import re
prefix_re = re.compile(r"(+|-|*|/)\s+(\d+)\s+(\d+)")
for line in get_lines_to_parse():
    match = prefix_re.match(line)
    if match:
        operand = match.group(1)
    if operand == '+':
        return int(match.group(2))+int(match.group(3))
    elif operand == '-':
        return int(match.group(2))-int(match.group(3))
#etc...
person pokstad    schedule 15.03.2011
comment
Он проверяет все ваше выражение, чтобы убедиться, что числа являются числами, а символы находятся в нужном месте. Выбранный вами метод разбиения не гарантирует правильного форматирования выражения. - person pokstad; 15.03.2011

На основе других ответов, но с меньшей логикой.

import operator

def eval_prefix(tokens):
    operators = {'+': operator.add, '-': operator.sub, '/': operator.truediv, 
                 '*': operator.mul, '%': operator.mod}

    stack = []
    for i in reversed(tokens):
        if i in operators:
            stack[-2] = operators[i](int(stack[-1]), int(stack[-2]))
            del stack[-1]
        else:
            stack.append(i)
    return stack[0]
person kumori    schedule 23.05.2014

Это еще один способ сделать это. Я добавил переключатель «@» для a, b и c, который возвращает b, если a положительный, и возвращает c, если a отрицательный. Я знаю, что это немного длинно и неэффективно, но я хотел, чтобы он был универсальным для всех операций.

def operatorhelper(index, answer):
        del currentsplitline[index + 2]
        del currentsplitline[index + 1]
        del currentsplitline[index]
        currentsplitline.insert(index, answer)
    infilelines = ["+ 2 3", " - 3 2", "* 2 3", "@ 1 3 4"]
    for line in infilelines:
        currentsplitline = line.split(" ")
        for i in range(len(currentsplitline)):
            try:
                currentsplitline[i] = int(currentsplitline[i])
            except:
                continue
        operatorindexes = [int(i) for i,x in enumerate(currentsplitline) if not type(x) == int]
        operatorindexes = operatorindexes[::-1]
        for index in operatorindexes:
            answer = 0
            if(isinstance(currentsplitline[index + 1], int) and isinstance(currentsplitline[index + 2], int)):
                operator = currentsplitline[index]
                nextnum = currentsplitline[index + 1]
                secondnum = currentsplitline[index + 2]
                if(operator == "+"):
                    answer = nextnum + secondnum
                    operatorhelper(index, answer)
                elif(operator == "-"):
                    answer = nextnum - secondnum
                    operatorhelper(index, answer)
                elif(operator == "*"):
                    answer = nextnum * secondnum
                    operatorhelper(index, answer)
                elif(operator == "@"):
                    if(isinstance(currentsplitline[index + 3], int)):
                        thirdnum = currentsplitline[index + 3]
                        del currentsplitline[index + 3]
                        if(nextnum >= 0):
                            answer = secondnum
                        else:
                            answer = thirdnum
                        operatorhelper(index, answer)
        print(currentsplitline[0])
person Krishnan Shankar    schedule 05.04.2019