Может ли этот постфиксный интерпретатор Python (обратная польская нотация) быть более эффективным и точным?
вот интерпретатор постфиксных обозначений Python, который использует стек для оценки выражений. Можно ли сделать эту функцию более эффективной и точной?
#!/usr/bin/env python
import operator
import doctest
class Stack:
"""A stack is a collection, meaning that it is a data structure that
contains multiple elements.
"""
def __init__(self):
"""Initialize a new empty stack."""
self.items = []
def push(self, item):
"""Add a new item to the stack."""
self.items.append(item)
def pop(self):
"""Remove and return an item from the stack. The item
that is returned is always the last one that was added.
"""
return self.items.pop()
def is_empty(self):
"""Check whether the stack is empty."""
return (self.items == [])
# Map supported arithmetic operators to their functions
ARITHMETIC_OPERATORS = {"+":"add", "-":"sub", "*":"mul", "/":"div",
"%":"mod", "**":"pow", "//":"floordiv"}
def postfix(expression, stack=Stack(), operators=ARITHMETIC_OPERATORS):
"""Postfix is a mathematical notation wherein every operator follows all
of its operands. This function accepts a string as a postfix mathematical
notation and evaluates the expressions.
1. Starting at the beginning of the expression, get one term
(operator or operand) at a time.
* If the term is an operand, push it on the stack.
* If the term is an operator, pop two operands off the stack,
perform the operation on them, and push the result back on
the stack.
2. When you get to the end of the expression, there should be exactly
one operand left on the stack. That operand is the result.
See http://en.wikipedia.org/wiki/Reverse_Polish_notation
>>> expression = "1 2 +"
>>> postfix(expression)
3
>>> expression = "5 4 3 + *"
>>> postfix(expression)
35
>>> expression = "3 4 5 * -"
>>> postfix(expression)
-17
>>> expression = "5 1 2 + 4 * + 3 -"
>>> postfix(expression, Stack(), ARITHMETIC_OPERATORS)
14
"""
if not isinstance(expression, str):
return
for val in expression.split(" "):
if operators.has_key(val):
method = getattr(operator, operators.get(val))
# The user has not input sufficient values in the expression
if len(stack.items) < 2:
return
first_out_one = stack.pop()
first_out_two = stack.pop()
operand = method(first_out_two, first_out_one)
stack.push(operand)
else:
# Type check and force int
try:
operand = int(val)
stack.push(operand)
except ValueError:
continue
return stack.pop()
if __name__ == '__main__':
doctest.testmod()
3 ответов
общие рекомендации:
- избегайте ненужных проверок типов и полагайтесь на поведение исключений по умолчанию.
-
has_key()
уже давно не рекомендуется в пользуin
оператор: используйте это вместо этого. -
профиль ваша программа, прежде чем пытаться оптимизировать производительность. Для запуска профилирования с нулевым усилием любого заданного кода просто запустите:
python -m cProfile -s cumulative foo.py
конкретные очки:
-
list
делает хороший стек из коробки. В частности, он позволяет использовать диапазонное представление (учебник), чтобы заменитьpop
/pop
/append
танец с одним шагом. -
ARITHMETIC_OPERATORS
может ссылаться на реализации операторов напрямую, безgetattr
косвенности.
положить все это вместе:
ARITHMETIC_OPERATORS = {
'+': operator.add, '-': operator.sub,
'*': operator.mul, '/': operator.div, '%': operator.mod,
'**': operator.pow, '//': operator.floordiv,
}
def postfix(expression, operators=ARITHMETIC_OPERATORS):
stack = []
for val in expression.split():
if val in operators:
f = operators[val]
stack[-2:] = [f(*stack[-2:])]
else:
stack.append(int(val))
return stack.pop()
списки могут использоваться непосредственно как стеки:
>>> stack = []
>>> stack.append(3) # push
>>> stack.append(2)
>>> stack
[3, 2]
>>> stack.pop() # pop
2
>>> stack
[3]
вы также можете поместить функции оператора непосредственно в свой ARITHMETIC_OPERATORS dict:
ARITHMETIC_OPERATORS = {"+":operator.add,
"-":operator.sub,
"*":operator.mul,
"/":operator.div,
"%":operator.mod,
"**":operator.pow,
"//":operator.floordiv}
затем
if operators.has_key(val):
method = operators[val]
кстати, использование списков в качестве стеков довольно общие идиоматический Python.
вы можете напрямую сопоставить операторы: {"+": operator.add, "-": operator.sub, ...}
. Это проще, не нужно лишних getattr
а также позволяет добавлять дополнительные функции (без взлома модуля оператора).
Вы также можете удалить несколько временных переменных, которые используются только один раз:
rhs, lhs = stack.pop(), stack.pop()
stack.push(operators[val](lhs, rhs)).
кроме того (меньше производительности и больше проблемы стиля, также субъективной), я бы не делал обработку ошибок вообще в цикле и обернуть его в один блок try с except KeyError
заблокировать ("неизвестный операнд"), к except IndexError
блок (пустой стек), ...
но точный? Дает ли это неправильные результаты?