2010-10-05 14 views
7

Aşağıda, ifadeleri değerlendirmek için bir yığın kullanan bir Python postfix notasyon yorumlayıcısı yer almaktadır. Bu işlevi daha verimli ve doğru yapmak mümkün mü?Bu Python postfix gösterimi (ters lehçe gösterimi) yorumcusu daha verimli ve doğru yapılabilir mi?

#!/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() 

cevap

10

Genel öneriler:

  • kaçının gereksiz tip kontroller ve varsayılan istisna davranış güveniyor. has_key(), in işlecinin lehine uzun zamandır kullanımdan kaldırılmıştır: bunun yerine kullanın. Herhangi bir performans optimizasyonu yapmaya başlamadan önce, programınızı
  • Profile programınıza ekleyin. Herhangi bir kod sıfır çaba profilleme çalışması için, sadece çalıştırın: python -m cProfile -s cumulative foo.py

Özgül noktaları:

  • listmakes a good stack kutunun dışında. Özellikle,/pop//append dansını tek bir adımla değiştirmek için dilim gösterimi (tutorial) kullanmanıza izin verir.
  • ARITHMETIC_OPERATORS, getattr indirmesi olmadan, doğrudan operatör uygulamalarına başvurabilir.

hep birlikte bu koyarak:

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() 
+1

Piet, bu harika bir cevap. Programın yürütme süresi azaldı. Ancak bir sorum var. Postfix'de, olası tüm istisnaları yakalar ve bunları özel bir "İstisna" olarak arayan kişiye taşır mıydınız veya kapsamlı bir deneme/istisna bloğunda aramayı 'postfix'e mi sardınız? – simeonwillbanks

+0

simeonwillbanks: İstisnalarla ilgili şüpheniz olduğunda, hiçbir şey yapmayın. :-) Bir sebepten ötürü olağanüstü! Kendi istisnalarınızı tanımlamak nadirdir: [yerleşik bir istisna] (http://docs.python.org/library/exceptions.html) yeterli olduğunda bunu yapmayın. Ayrıca, "kapsamlı" deneme/hariç blokları yazmak zorunda kalmayın: hemen hemen tüm kodlar hemen hemen tüm istisnaların geçmesine izin vermeli ve yalnızca * bilmeleri gereken istisnaları ele almalıdır. (Bir yakalama özel durum işleyicisinin olmasını istediğiniz yegane yerlerden biri, bir üretim uygulamasının en üst düzeyindedir, örneğin, hatayı bildirmek için.) –

+0

Piet, ipuçları için teşekkürler. Çözümü kabul ediyorum! Not; Geçtiğimiz yaz 2010 Dünya Kupası için Cape Town'ı ziyaret ettim. Güzel bir şehir ve bir gün geri dönmeyi umuyorum. – simeonwillbanks

3

Listeler yığınlar olarak doğrudan kullanılabilir:

Ayrıca ARITHMETIC_OPERATORS doğrudan operatör fonksiyonları koyabilirsiniz
>>> stack = [] 
>>> stack.append(3) # push 
>>> stack.append(2) 
>>> stack 
[3, 2] 
>>> stack.pop() # pop 
2 
>>> stack 
[3] 

dict: o zaman

ARITHMETIC_OPERATORS = {"+":operator.add, 
         "-":operator.sub, 
         "*":operator.mul, 
         "/":operator.div, 
         "%":operator.mod, 
         "**":operator.pow, 
         "//":operator.floordiv} 

if operators.has_key(val): 
    method = operators[val] 

Amacı Se, şeyleri daha verimli hale getirmemek (bu etkiye sahip olsa da) ancak okuyucunun daha açık olmasını sağlamaktır. Gereksiz indirme ve sarmalayıcı seviyelerinden kurtulun. Bu, kodunuzu daha az gizlemeye eğilimlidir. Ayrıca performansta (önemsiz) iyileştirmeler sağlayacaktır, ancak bunu ölçmedikçe, buna inanmayın.

Bu arada, yığınları liste olarak kullanmak common deyimsel Python'dur.

+0

Mükemmel noktayı. Yerleşik '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' Mac'imde (OS X 10.5.8/2.6 GHz Intel Core 2 Duo/4GB Mem), yerleşik yığın ya 0.18 saniyede ya da 0.19 saniyede çalışır. Sürekli olarak, yığın ADT 0.19 saniyede çalışır. – simeonwillbanks

2

Operatörleri doğrudan eşleştirebilirsiniz: {"+": operator.add, "-": operator.sub, ...}. Bu daha basittir, gereksiz getattr'a ihtiyaç duymaz ve ayrıca ek işlevler eklemeyi sağlar (operatör modülünü kesmeden).

Ayrıca
rhs, lhs = stack.pop(), stack.pop() 
stack.push(operators[val](lhs, rhs)).  

(sübjektif ayrıca, bir stil sorununun bir performans az ve daha fazla), ben de hata işlemeye yapmayın Propably olacaktır: Ayrıca sadece bir kez zaten kullanılmaktadır birkaç geçici değişkenleri düşebilir tüm döngü içinde ve bir except KeyError bloğu ("Bilinmeyen işlenen"), bir except IndexError blok (boş yığın), bir blok içinde sarın, ...

Ama doğru? Yanlış sonuçlar veriyor mu?

+0

Hata işleme işine ara verildi. Python istisnaları C++ istisnaları değildir. – nmichaels

+0

Öneriler için teşekkürler. Bütün doktrinler geçiyor, ama kapsamlı olmayabilirler. Belki hala bir böcek var? – simeonwillbanks

+2

'stack.push (işleçler [val] (stack.pop(), stack.pop())' bu işleç, çünkü func, işlevin ilk argümanı ikinci argüman olarak beklediği içindir. Aslında, bu satırı kodladım: 'method (stack.pop(), stack.pop())', ancak doctest'ler başarısız oldu. – simeonwillbanks