/ / Czy jest lepszy sposób na wdrożenie? - python, implementacja, kalkulator

Czy istnieje lepszy sposób na wdrożenie tego? - python, implementacja, kalkulator

Piszę kalkulator w Pythonie (jako ćwiczenie), a jest jeszcze jedna rzecz, nad którą się zastanawiam.

Program dzieli dane wejściowe na listę liczb i operatorów. Wynik jest obliczany w ten sposób:

import operator
ops = {"+" : operator.add,                      # operators and corresponding functions
"-" : operator.sub,
"*" : operator.mul,
"/" : operator.truediv,
"%" : operator.mod}
precedence = [["*", "/", "%"], ["+", "-"]]      # order of precedence for operators

def evaluate(exp):
for oplist in precedence:                     # search for operators first in order of precedence
for op in exp:                              # then from left to right
if op in oplist:
index = exp.index(op)
result = ops[op](exp[index - 1], exp[index + 1])
# compute the result of the operation
exp[index - 1:index + 2] = [result]
# replace operation and operands with result
return exp[0]

# for example,
evaluate([2, "+", 3, "+", 4, "+", 5])
# should return 14

Ta funkcja sprawdza listęoperatory arytmetyczne w kolejności malejącego priorytetu, a następnie od lewej do prawej, a gdy znajdzie takiego operatora, wywołuje odpowiednią funkcję na sąsiednich elementach listy (operandy) i zastępuje operator i operandy na liście wynikiem operacji . Po wykonaniu wszystkich operacji lista będzie zawierać pojedynczy element - wynik obliczeń.

Jednak ta funkcja nie zachowuje się w ten sposóbjest przeznaczony. Problem (myślę) polega na tym, że ta funkcja modyfikuje listę (przypisując ją do plasterków) podczas jej iteracji. Już znalazłem rozwiązanie tego problemu tutaj (poprzez ponowne uruchomienie wewnętrznego for pętla za każdym razem, gdy lista jest modyfikowana), ale ludzie, którzy dali rozwiązanie, zdawali się myśleć, że zwykle powinien być lepszy sposób na osiągnięcie tego, co jest potrzebne.

Zastanawiałem się, czy istnieje lepszy sposób na zaimplementowanie tego algorytmu, który unika dziwnego „ponownego uruchamiania pętli”.

Dzięki za wszelkie pomysły!

Odpowiedzi:

3 dla odpowiedzi № 1

Wydaje mi się, że inaczej bym to zrobił i użyłem funkcji rekurencyjnej. Wyrzuć operacje i zastąp je wynikiem ich oceny.

import operator

ops = {
"+" : operator.add,
"-" : operator.sub,
"*" : operator.mul,
"/" : operator.truediv,
"%" : operator.mod,
}

precedence = [
set(["*", "/", "%"]),
set(["+", "-"]),
]

def evaluate(expr):
# if len == 3 then just return result of expression
if len(expr) == 3:
l, op, r = expr
return ops[op](l, r)
else:
for op_list in precedence:
for op in expr:
if op in op_list:
# find index of first operation
idx = expr.index(op)-1
# pop off and evaluate first matching operation in expr
result = evaluate([expr.pop(idx) for i in range(3)])
# insert result back into expr
expr.insert(idx, result)
return evaluate(expr)

3 dla odpowiedzi № 2

Możesz manipulować indeksem, jeśli używasz a while zamiast tego pętla

def evaluate(exp):
for oplist in precedence:  # search for operators first in order of precedence
idx = 0
while idx < len(exp):
op = exp[idx]
if op in oplist:
result = ops[op](exp[idx - 1], exp[idx + 1])
exp[idx - 1:idx + 2] = [result]
idx -= 1       # move index back since list is shortened by 2
else:
idx += 1
return exp[0]

2 dla odpowiedzi nr 3

„Nie jestem pewien, co masz na myśli,” uruchamiając ponownieW tym konkretnym przypadku wydaje mi się, że powinieneś po prostu zastosować funkcję wielokrotnie do wyrażenia, aż zostanie ona zredukowana do wartości. Jest to mniej wydajne niż mogłoby być, ale działa i jest jasne. ...

def find_op(tokens, oplist):
for i, token in enumerate(tokens):
if token in oplist:
return i
else:
return -1

def reduce_binary_infix(tokens, i, ops):
op = ops[tokens[i]]
tokens[i - 1: i + 2] = [op(tokens[i - 1], tokens[i + 1])]
return tokens

def evaluate(tokens, ops, precedence):
for prec in precedence:
index = find_op(tokens, prec)
while index >= 0:
tokens = reduce_binary_infix(tokens, index, ops)
index = find_op(tokens, prec)
return tokens

print evaluate([2, "+", 3, "+", 4, "+", 5], ops, precedence)

Przetestowany:

>>> print evaluate([2, "+", 3, "+", 4, "+", 5], ops, precedence)
[14]

Można to uczynić bardziej wydajnym, nie przeszukując całego ciągu wielokrotnie. Można to zrobić, mając start_index parametr w find_opi przez posiadanie reduce_binary_infix zwróć nowy bieżący indeks wraz ze zmniejszoną listą.

Jest to również trochę bardziej gadatliwe niż to, co masz, ale myślę, że to pomaga czytelności kodu - nie wspominając o jego wielokrotnym użyciu.