2016-05-09 9 views
0

Ich implementiere ein Programm, das eine RPN-Rechner-Funktion benötigt, ich habe die eine unten, aber neu zu Python Ich frage mich, ob ich es optimieren kann, ohne die Lesbarkeit zu verlieren.RPN Evaluator Optimierung ohne Lesbarkeit zu verlieren

einige Lösungen gefunden Wörterbücher und so weiter, aber ich habe in den ‚Pythonesque‘ Teile verloren, sind die Listen noch so etwas wie ein Geheimnis zu mir ...

meine Funktion ist:

def parseRPN(expression): 
    """Parses and calculates the result fo an RPN expression 
     takes a list in the form of ['2','2','*'] 
     returns 4 
    """ 
    try: 
     stack = [] 
     for val in expression: 
      if val in ['-', '+', '*', '/']: 
       op1 = stack.pop() 
       op2 = stack.pop() 
       if val=='-': result = op2 - op1 
       if val=='+': result = op2 + op1 
       if val=='*': result = op2 * op1 
       if val=='/': 
        if op1==0: 
         result=1 
        else: 
         result = op2/op1 
       stack.append(result) 
      elif val in ['sin','cos']: 
       op1 =stack.pop() 
       if val=='sin': result = sin(op1) 
       if val == 'cos': result = cos(op1) 
       stack.append(result) 
      else: 
       stack.append(float(val)) 
     return stack.pop() 
    except: 
     print('error parse RPN fn:parse_rpn :' + str(expression)) 
     return 10*10**10 

Vielen Dank im Voraus

+0

Der grundlegende Ansatz ist in Ordnung. Wenn es funktioniert, warum willst du dich damit anlegen? Brauchst du es wirklich, um schneller zu sein? Unter der Annahme, dass der Formeleintrag von einem Benutzer kommt, der tippt, wird die Antwort sicherlich sofort für alle praktischen Zwecke sein. Und wenn Sie an eine Änderung denken, die den Code schwerer lesbar und verständlich macht, gehen Sie einfach nicht dorthin. –

+0

Hallo. Diese Funktion wird tausende Male von anderen Funktionen aufgerufen. Es sieht auch gut für mich aus. Aber da ich neu in Python bin, könnte es etwas geben, was ich tat, was einen großen Leistungsabfall zur Folge hatte. –

+0

Macht Sinn, danke für die zusätzlichen Informationen. Ich würde es immer noch versuchen und sehen, ob es schnell genug ist, bevor ich etwas anderes mache. Bei einer Schätzung würde ich erwarten, dass die Umwandlung von string in float '(float (val))' hier länger dauert als alles andere. –

Antwort

2

Die ursprüngliche Implementierung ist gut und klar. Hier ist eine andere, die mehr Pythonic Funktionen verwendet:

  • Tests mit py.test (< 3)

  • Parse-Fehler in Ruhe gelassen werden, da sie bereits zeigen, was los ist

  • das operator Modul wird direkt für viele Zwei-Argument-Funktionen verwendet, wie Multiplikation

  • ebenfalls, ein-Argument mathematische Funktionen wie sin/cos nur rufen Sie die math Bibliothek

  • der Einfachheit halber kann der Ausdruck als eine Zeichenkette angegeben werden, wie "2 3 /"

Rechner sind viel Spaß und sind eine großartige Einführung Themen zu kühlen, wie Kompilieren und Parsing. Habe Spaß!

RPN-Rechner

import math 
import operator 

import pytest 


ERROR_VALUE = -1. 


def safe_divide(darg1, darg2): 
    try: 
     return darg1/darg2 
    except ZeroDivisionError: 
     return ERROR_VALUE 

def parseRPN(expression): 
    """Parses and calculates the result of a RPN expression 
     takes a list in the form of ['2','2','*'] 
     returns 4 
    """ 
    # allow simple string: "2 3 /" 
    if isinstance(expression, basestring): 
     expression = expression.split() 

    function_twoargs = { 
    '*': operator.mul, 
    '/': safe_divide, 
    '+': operator.add, 
    '-': operator.sub, 
    } 
    function_onearg = { 
    'sin': math.sin, 
    'cos': math.cos, 
    } 
    stack = [] 
    for val in expression: 
     result = None 
     if val in function_twoargs: 
      arg2 = stack.pop() 
      arg1 = stack.pop() 
      result = function_twoargs[val](arg1, arg2) 
     elif val in function_onearg: 
      arg = stack.pop() 
      result = function_onearg[val](arg) 
     else: 
      result = float(val) 
     stack.append(result) 
    return stack.pop() 


def test_happy_paths(): 
    assert parseRPN([2, 3, '*']) == 6. 
    assert parseRPN('0 sin') == 0. 
    assert parseRPN([2, 3, '/']) == 2./3 

def test_safe_divide(): 
    assert parseRPN([2, 0, '/']) == ERROR_VALUE 

def test_parse_error(): 
    with pytest.raises(ValueError) as excinfo: 
     parseRPN('gin') 
    assert str(excinfo.value) == 'could not convert string to float: gin' 
+0

Danke, die Geschwindigkeit Verbesserung ist vernachlässigbar über meine Umsetzung, ich denke, ich werde es so lassen wie es ist. –

+1

Nun, da ich kompliziertere RPN-Ausdrücke verwende, wird Ihre Implementierung besser funktionieren: D DANKE –

1

Dies ist meine Version:

import operator 
import math 

_add, _sub, _mul = operator.add, operator.sub, operator.mul 
_truediv, _pow, _sqrt = operator.truediv, operator.pow, math.sqrt 
_sin, _cos, _tan, _radians = math.sin, math.cos, math.tan, math.radians 
_asin, _acos, _atan = math.asin, math.acos, math.atan 
_degrees, _log, _log10 = math.degrees, math.log, math.log10 
_e, _pi = math.e, math.pi 
_ops = {'+': (2, _add), '-': (2, _sub), '*': (2, _mul), '/': (2, _truediv), 
     '**': (2, _pow), 'sin': (1, _sin), 'cos': (1, _cos), 'tan': (1, _tan), 
     'asin': (1, _asin), 'acos': (1, _acos), 'atan': (1, _atan), 
     'sqrt': (1, _sqrt), 'rad': (1, _radians), 'deg': (1, _degrees), 
     'ln': (1, _log), 'log': (1, _log10)} 
_okeys = tuple(_ops.keys()) 
_consts = {'e': _e, 'pi': _pi} 
_ckeys = tuple(_consts.keys()) 


def postfix(expression): 
    """ 
    Evaluate a postfix expression. 

    Arguments: 
     expression: The expression to evaluate. Should be a string or a 
        sequence of strings. In a string numbers and operators 
        should be separated by whitespace 

    Returns: 
     The result of the expression. 
    """ 
    if isinstance(expression, str): 
     expression = expression.split() 
    stack = [] 
    for val in expression: 
     if val in _okeys: 
      n, op = _ops[val] 
      if n > len(stack): 
       raise ValueError('not enough data on the stack') 
      args = stack[-n:] 
      stack[-n:] = [op(*args)] 
     elif val in _ckeys: 
      stack.append(_consts[val]) 
     else: 
      stack.append(float(val)) 
    return stack[-1] 

In den ersten fünf Zeilen nach den Einfuhren I lokale Namen für die Operatoren und Konstanten als Optimierungs machen, so dass wir nicht tun müssen jedes Mal eine Modulsuche durchführen, wenn wir eine verwenden. Das Zulassen von Konstanten wie e und pi ist eine zusätzliche Funktion.

Das ops Wörterbuch ist Schlüssel zum Arbeiten des Rechners. Es verknüpft das Symbol eines Operators mit einem Tupel, das die Anzahl der vom Operator konsumierten Argumente und die Aufruffunktion enthält. Aufgrund dieser Datenstruktur müssen Operatoren nicht speziell nach ihrer Anzahl von Argumenten behandelt werden.

Außerdem speichern wir die Schlüssel für die _ops und _consts dicts in Tupeln, da wir diese sehr oft verwenden werden.

Die Linie stack[-n:] = [op(*args)] ist das Herz des Rechners. Es enthält zwei Tricks. Zuerst entpackt es das Argument mit dem Operator *. Zweitens ersetzt es mehrere Werte auf dem Stapel mit dem Ergebnis op.

Absichtlich erfasst diese Funktion keine Ausnahmen, die durch Fehler in den Eingabedaten verursacht werden.

Verwandte Themen