Può questo postfix notazione Python (notazione polacca inversa) interprete essere reso più efficiente e preciso?

StackOverflow https://stackoverflow.com/questions/3865939

  •  28-09-2019
  •  | 
  •  

Domanda

Ecco un Python postfix notazione interprete che utilizza uno stack per valutare le espressioni. E 'possibile per rendere questa funzione più efficiente e preciso?

#!/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()
È stato utile?

Soluzione

suggerimenti generali:

  • Evitare di controlli di tipo non necessarie, e si basano sul comportamento eccezione di default.
  • has_key() è stato a lungo deprecato in favore dell'operatore in: utilizzarlo.
  • Profilo vostro programma, prima di tentare qualsiasi ottimizzazione delle prestazioni. Per un-zero sforzo profilatura run di qualsiasi codice, basta eseguire: python -m cProfile -s cumulative foo.py

punti specifici:

Mettere insieme tutto questo:

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

Altri suggerimenti

Gli elenchi possono essere utilizzati direttamente come stack:

>>> stack = []
>>> stack.append(3) # push
>>> stack.append(2)
>>> stack
[3, 2]
>>> stack.pop() # pop
2
>>> stack
[3]

Si può anche mettere le funzioni operatore direttamente nel tuo ARITHMETIC_OPERATORS dict:

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

poi

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

L'obiettivo di questi non è quello di rendere più efficiente le cose (anche se può avere questo effetto), ma per renderli più evidenti al lettore. Sbarazzarsi di livelli inutili di indirezione e involucri. Che tenderà a rendere il codice meno offuscato. Esso fornirà anche miglioramenti (banale) in termini di prestazioni, ma non credo che a meno che non lo si misura.

Per inciso, utilizzando liste come pile è abbastanza comune idiomatica Python.

È possibile mappare direttamente agli operatori: {"+": operator.add, "-": operator.sub, ...}. Questo è più semplice, non ha bisogno del getattr inutile e permette anche l'aggiunta di funzioni supplementari (senza l'hacking del modulo operatore). Si potrebbe anche cadere qualche variabili temporanee che vengono utilizzati solo una volta in ogni caso:

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

Anche (meno di una performance e più di un problema di stile, anche soggettiva), sarei stata riordinata non fare gestione degli errori a tutti nel ciclo e avvolgerla in un blocco try con un blocco except KeyError ( "operando sconosciuto" ), un blocco except IndexError (stack vuoto), ...

Ma accurata? Dà risultati errati?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top