Hi auch,
so dumm sind Deine Fragen gar nicht. Ich bin sicher, viele hier im Forum haben die gleichen Fragen und harren der Antworten.
Erstmal legen wir die Begriffe fest.
Ein
Operator ist "+", "-", "*", "/" oder "**"
Ein
Ausdruck ist entweder
eine Zahl, oder
beliebig viele Ausdrücke, die auch in Klammern stehen können, die durch jeweils einen
Operator getrennt sind.
Der obige Satz, enthält in der Erklärung wieder das Substantiv (Ausdruck).
Hier ist schon die Recursion zu erahnen, die uns später beim Aufbau des Rechenbaumes wieder begegnen wird.
Dein Ansatz ist zwar naheliegend, aber das, für den Menschen, naheliegende ist nicht immer ideal wenns ein Computer machen muss.
Schauen wir uns mal einen recht komplexen Ausdruck an.
wie würdest Du von hand einen solchen Ausdruck berechnen?
sicher, indem du den Ausdruck zerlegst. Als Mensch siehtst Du auf einen Blick, daß du die erste Rechnung
3+4 gleich ausrechnen kannst, weil danach ein - kommt. auch können die innersten Klammern
2+3 und
3+8 gleich ausgerechnet werden. Das ergibt dann den neuen Ausdruck:
jetzt kann der Ausdruck in der Klammer betrachtet werden, und hier die Multiplikationen ausgeführt werden.
bleibt dann noch übrig.
jetzt kann der Ausdruck in der klammer einfach ausgerechnet werden.
noch die letzte Multiplikation
wenn wir das jetzt noch ausrechnen bekommen wir 394 als Ergebnis.
Nun ist ein Computer aber kein Mensch, Der Computer arbeitet im Gegensatz zum Gehirn, welches die Daten parallel verarbeitet, streng linear. Also immer eins nach dem anderen.
Allerdings haben wir eines schon beim rechenen "wie ein Mensch" gesehen, jeder beliebig komplexe Ausdruck lässt sich in einfachere Ausdrücke zerlegen, die dann mit den Operatoren (+-*/**) verbunden sind.
Frech wie wir sind klauen wir mal beim alten Cäsar, der schon gesagt haben soll "Divide et impera!" - was meisst mit "Teile und herrsche" übersetzt wird.
Da sich ein Ausdruck aus Ausdrücken zusammensetzt (ausser er besteht aus einer einzelen Zahl) können wir ihn aufteilen und die resultierenden Teile wieder als Ausdrücke bearbeiten, also wieder aufteilen, und so weiter, bis wir bei lauter einzelnen Zahlen und Operatoren sind.
Vorher müssen wir uns noch überlgen, wie wir die Operatoren (+,-,*,/,**) mit Prioritäten versehen können, um gleich "Punkt vor Strich" zu berücksichtigen. Am einfachsten legen wir uns ein Dictionary an, das die Operatoren als Schlüssel verwendet und jeder Operator bekommt als Wert eine Priorität.
Code: Alles auswählen
op_pri = {"+" : 0, "-" : 0, "*" : 1, "/" : 1, "**" : 2}
Man könnte sagen "Je höher die Priorität, desto 'enger' werden die Ausdrücke durch den Operator aneinander gebunden". Also umso früher müssen die Ausdrücke die Durch den Operator getrennt sind berechnet werden. Bevor die Ausdrücke mit niedrigerer Priorität an die Reihe kommen.
Operatoren mit gleicher Priorität und auf gleicher Klammerebene, werden in der Reihenfolge des Auftretens im Ausdruck berechnet, also der am weitesten links stehende zuerst.
Intuitiv warst Du bei Deinem Beispiel schon auf der richtigen Fährte. Um einen gegebenen Ausdruck aufzuteilen, suchen wir eben nicht, den Operator mit der höchsten Priorität, sonder den mit der niedrigsten, und erhalten so einen linken Ausdruck, den Operator, und einen rechten Ausdruck. Aus
"1+2*3" wird so
"1"+"2*3".
Wenn wir jetzt unseren Rechenbaum aufbauen, erstellen wir einen Knoten, dieser enthält dann als self.value das "+", und für den linken zweig, wird ein Knoten erstellt, der dann als self.value die "1" enthält, und für den rechten Zweig einen Knoten der aus dem rechten Teilausdruck "2*3" seine Daten bezieht.
Also die Init-Methode unserer Rechenbaumknoten, bekommt einen Ausdruck, parst und, wenn es sich nicht um eine einzelne Zahl handelt, zerlegt sie den Ausdruck, beim Operator mit der kleinsten Priorität, und erstellt sich für den linken und rechten Ast weitere Knoten.
Hier mal der ganze Code zur Berechnung von Ausdrücken mittels Rechenbaum.
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
op_pri = {"+" : 0, "-" : 0, "*" : 1, "/" : 1, "**" : 2}
def find_parenthesis(ausdruck, index):
cnt = 1 # Zähler für Klammern
if ausdruck[index] == "(":
for i in xrange(index+1, len(ausdruck)):
if ausdruck[i] == "(":
cnt += 1
elif ausdruck[i] == ")":
cnt -= 1
if cnt == 0: # schliessende Klammer gefunden
break # fertig, i enthält den Index
elif ausdruck[index] == ")":
for i in xrange(index-1, -1,-1):
if ausdruck[i] == ")":
cnt += 1
elif ausdruck[i] == "(":
cnt -= 1
if cnt == 0: # öffnende Klammer gefunden
break # fertig, i enthält den Index
if cnt != 0:
raise ValueError("unbalanced parentesis")
return i
def parse(ausdruck):
op = "" # wir haben noch keinen Operator
op_at = -1
if ausdruck[0] in "+-":
i = 1 # zeichen an position 0 ist nur ein Vorzeichen
else:
i = 0
while i < len(ausdruck):
# Wenn eine Klammer, dann überspringen.
if ausdruck[i] == "(":
i = find_parenthesis(ausdruck, i)+1
if i >= len(ausdruck):
break
if ausdruck[i] in "+-*/": # Operator gefunden
if ausdruck[i:i+2] == "**": # Potenz behandeln
found = "**"
else: # normaler ein-zeichen Operator
found = ausdruck[i]
if op: # wir hatten schon einen Operator gefunden
# wenn priorität des neuen Operators <= dem Alten
if op_pri[found] <= op_pri[op]:
op = found # neuer Operator hat kleinere Priorität
op_at = i # also nehmen wir den
else:
op = found # unser erster gefundener operator
op_at = i
i += len(found) # wegen '**' sonst würde 1 statt len(found) genügen
if ausdruck[i] in "+-": # Eventuelles vorzeichen der nächsten Zahl
i += 1 # überspringen
else: # kein Operator
i += 1 # gehe zum nächsten zeichen
if op_at >= 0:
# Operator und linken und rechten Ausdruck zurückgeben.
return op, ausdruck[:op_at], ausdruck[op_at+len(op):]
else:
print "Fehler beim parsen von %s" % ausdruck
return None, None, None
class Node(object):
def __init__(self, ausdruck):
# falls ganzer Ausdruck geklammert, klammern entfernen
while (ausdruck[0] == "(" and
find_parenthesis(ausdruck, 0) == len(ausdruck)-1):
ausdruck = ausdruck[1:-1]
try: # versuche ob 'ausdruck' eine Zahl darstellt
self.data = int(ausdruck)
self.left = None
self.right = None
except ValueError: # 'ausdruck' ist keine Zahl!
data, left, right = parse(ausdruck)
self.data = data
self.left = Node(left)
self.right = Node(right)
def GetValue(self):
if type(self.data) is int:
return self.data
else:
if self.data == "+":
return self.left.GetValue() + self.right.GetValue()
elif self.data == "-":
return self.left.GetValue() - self.right.GetValue()
if self.data == "*":
return self.left.GetValue() * self.right.GetValue()
elif self.data == "/":
return self.left.GetValue() / self.right.GetValue()
elif self.data == "**":
return self.left.GetValue() ** self.right.GetValue()
else:
return "Fehler: unbekannter Operand %s" % self.data
def print_me(self, depth=0):
indent = " "*depth
if self.left:
self.left.print_me(depth+1)
print indent+str(self.data)
if self.right:
self.right.print_me(depth+1)
if __name__ == "__main__":
import sys
while 1:
ausdruck = raw_input("Ausdruck: ")
if ausdruck == "":
sys.exit()
rechenBaum = Node(ausdruck)
print "Rechenbaum:"
rechenBaum.print_me()
print "Ergebnis:", rechenBaum.GetValue()
Ich denke, damit hast du ersmal ne weile zu tun beim Testen und rumspielen. Wenn Du glaubst, das Prinzip verstanden zu haben, kannst Du ja versuchen das Programm um die module-Operation "%" welche den ganzzahligen Rest einer Division liefert zu ergänzen.
Gruß
Dookie
P.S.: Du hast Glück, daß ich vor kurzem ein ähnliches Modul erstellt habe, so brauchte ich nur die relevanten Teile zu kopieren und auf int statt float umzustricken.