Taschenrechner (mit strings)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
microkernel
User
Beiträge: 271
Registriert: Mittwoch 10. Juni 2009, 17:27
Wohnort: Frankfurt
Kontaktdaten:

Hallo,
ich habe folgendes Problem ich möchte gerne aus einem string (z.B.: "45 * 9 - 9 + 2.3") eine mathematische rechnung machen. Hatte es schonmal mit folgenden Code versucht, hat aber nicht wirklich funktioniertund ich glaube es geht auch wesentlich einfacher.

Code: Alles auswählen

import re


" Math functions--------------------"
def plus(int1, int2):
    return float(int1 + int2)

def minus(int1, int2):
    return float(int1 - int2)

def multiply(int1, int2):
    return float(int1 * int2)

def square(int1, int2):
    return float(int1 ** int2)

def root(int1, int2):
    return float(int1 // int2)


" Main functions--------------------"
def get_operators(string):
    global oprt_plattern
    return re.findall(oprt_plattern, string)

def get_numbers(string):
    global numbers_plattern
    return re.findall(numbers_plattern, string)

def calculate(string):
    global operators
    global tmp_result
    string_numbers = get_numbers(string)
    string_operators = get_operators(string)

    result = int(string_numbers[0])
    string_numbers.pop(string_numbers.index(string_numbers[0]))

    for None_ in range(len(string_numbers)):
        result = operators[string_operators[0]] (result, int(
            string_numbers[0]))
        string_operators.pop(string_operators.index(string_operators[0]))
        string_numbers.pop(string_numbers.index(string_numbers[0]))
    return tmp_result
            

" Vars---------------------------------"
oprt_plattern = r"[+-/*(//)(**)]{1,}"
numbers_plattern = r"\d{1,}"

operators = {
    "+" : plus,
    "-" : minus,
    "*" : multiply,
    "**" : square,
    "//" : root,
    }

Hätte da jemand von ecuh ne Idee wie man es einfacher macht?

lg
microkernel
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Nur kurz zum Code: Die `global`s sind unnötig. `range(len(x))` macht man lieber mit `enumerate`. Mathematische Operationen gibt es in Funktionsform in `operator`.

Gruß
problembär

Hätte da jemand von ecuh ne Idee wie man es einfacher macht?
Nun ja:

Code: Alles auswählen

print eval("45 * 9 - 9 + 2.3")
:wink:
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

problembär hat geschrieben: Nun ja:

Code: Alles auswählen

print eval("45 * 9 - 9 + 2.3")
:wink:
Die Verwendung von eval in diesem Zusammenhang ist nicht empfohlen.
Nähere Informationen und Lösungsansätze in folgendem Thread:
http://www.python-forum.de/viewtopic.ph ... 07d5c51dfc
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

"root" und "square" sind als Namen hier nicht unbedingt passend.

Kommentare beginnt man übrigens mit einem # und benutzt keine Strings dazu.

Reguläre Ausdrücke solltest du dir besser auch noch einmal anschauen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

ice2k3 hat geschrieben:Die Verwendung von eval in diesem Zusammenhang ist nicht empfohlen.
Dieser Hinweis kommt an solchen Stellen so sicher wie das Amen in der Kirche. Aber mal nüchtern betrachtet: Ist das Programm nur für den Eigenbedarf oder eine bestimmte (näher zu spezifizierende) Gruppe von Anwendern gedacht, dann geht von der Verwendung von eval() wahrhaftig keine Gefahr aus.

@microkernel: Dein Code ist ziemlich wüst. Wenn ich nichts übersehen habe, hast du z.B. die Ausführungspriorität der Operatoren gar nicht berücksichtigt.

Der saubere Weg wäre nach meinem (laienhaften) Kenntnisstand die Umwandlung der Infix-Notation in Postfix-Notation (RPN) und danach eine stackbasierte Auswertung. Zum Einlesen: http://en.wikipedia.org/wiki/Shunting-yard_algorithm
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

numerix hat geschrieben: Der saubere Weg wäre nach meinem (laienhaften) Kenntnisstand die Umwandlung der Infix-Notation in Postfix-Notation (RPN) und danach eine stackbasierte Auswertung. Zum Einlesen: http://en.wikipedia.org/wiki/Shunting-yard_algorithm
Und warum nicht parsen mit ast? Damit ist die Reihenfolge schon richtig...
Funktionierendes Beispiel ist in dem angegebenen Thread ja schon vorhanden.
numerix hat geschrieben: Dieser Hinweis kommt an solchen Stellen so sicher wie das Amen in der Kirche.
Ja, aber er ist auch notwendig ;) Wenn man das ``aval`` einsetzt, sollte man mit dieser Problematik auf jeden Fall vertraut sein. Wobei ich deinen Argumenten natürlich auch zustimme...
Ronnie
User
Beiträge: 73
Registriert: Sonntag 21. März 2004, 17:44

numerix hat geschrieben:Der saubere Weg wäre nach meinem (laienhaften) Kenntnisstand die Umwandlung der Infix-Notation in Postfix-Notation (RPN) und danach eine stackbasierte Auswertung. Zum Einlesen: http://en.wikipedia.org/wiki/Shunting-yard_algorithm
Vielen Dank, den kannte ich noch gar nicht! Dort findet man (u.a.) eine Python-Implementierung: http://en.literateprograms.org/Shunting ... 8Python%29 :D
EDIT:
ice2k3 hat geschrieben:Und warum nicht parsen mit ast? Damit ist die Reihenfolge schon richtig...
Funktionierendes Beispiel ist in dem angegebenen Thread ja schon vorhanden.
Auch sehr schick! Eine wahre Goldgrube der Thread!
Achtung: User ist ein Python-Lehrling!
Benutzeravatar
microkernel
User
Beiträge: 271
Registriert: Mittwoch 10. Juni 2009, 17:27
Wohnort: Frankfurt
Kontaktdaten:

Vielen dank!
Hab es jetzt hinbekommen.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

microkernel hat geschrieben:Vielen dank! Hab es jetzt hinbekommen.
Ich schätze mit eval() ... :D

Wer seine Infix->Postfix Implementation mal testen will: http://www.spoj.pl/problems/ONP/

Wem das nicht genügt: http://www.spoj.pl/problems/MMASS/
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Postfix-Generator und Taschenrechner in einem (mit ast):
http://paste.pocoo.org/show/145261/
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

ice2k3 hat geschrieben:Postfix-Generator und Taschenrechner in einem (mit ast):
http://paste.pocoo.org/show/145261/
Sieht prima aus!
Benutzeravatar
microkernel
User
Beiträge: 271
Registriert: Mittwoch 10. Juni 2009, 17:27
Wohnort: Frankfurt
Kontaktdaten:

numerix hat geschrieben:
microkernel hat geschrieben:Vielen dank! Hab es jetzt hinbekommen.
Ich schätze mit eval() ... :D

Wer seine Infix->Postfix Implementation mal testen will: http://www.spoj.pl/problems/ONP/

Wem das nicht genügt: http://www.spoj.pl/problems/MMASS/
:D ja zuerst schon aber dank ice2k3's code jetzt mit dem Shunting-yard-algorythmus. Ich habe allerdings immer noch nicht so recht verstanden warum "eval()" nicht so empfehlenswert ist.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ist niemand aufgefallen, dass `oprt_plattern` falsch ist? Das match nie und nimmer, was gemeint ist? Wie auch immer, hier ein klassischer rekursiv absteigender Parser:

Code: Alles auswählen

import re

operators = {
    "+": lambda a, b: a + b,
    "-": lambda a, b: a - b,
    "*": lambda a, b: a * b,
    "/": lambda a, b: a / b,
    "**": lambda a, b: a ** b,
}

class Parser(object):
    def __init__(self, s):
        def scan(s):
            for m in re.finditer(r"(\d+)|(\*\*|[-+*/()])", s):
                yield m.group()
            yield ""
        self.scanner = scan(s)
        self.token = self.scanner.next()
    
    def advance(self):
        token, self.token = self.token, self.scanner.next()
        return token
    
    def eval(self):
        r = self.eval_factor()
        while self.token in ("+", "-"):
            r = operators[self.advance()](r, self.eval_factor())
        return r
    
    def eval_factor(self):
        r = self.eval_power()
        while self.token in ("*", "/"):
            r = operators[self.advance()](r, self.eval_power())
        return r
    
    def eval_power(self):
        r = self.eval_value()
        if self.token == "**":
            r = operators[self.advance()](r, self.eval_power())
        return r
    
    def eval_value(self):
        if self.token.isdigit():
            return int(self.advance())
        if self.token == "(":
            self.advance()
            r = self.eval()
            if self.advance() != ")": raise SyntaxError
            return r
        raise SyntaxError

print Parser("(3+4*5)**2").eval()
Stefan
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

microkernel hat geschrieben:mit dem Shunting-yard-algorythmus
Also wenn schon nicht "Algorithmus", dann wenigstens "Algo-Rhythmus" ...
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

sma: Gefällt dir das operator-Modul etwa nicht? (add, sub, mul, (true)div, pow)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich meide diese Extra-Imports. Sie hätten nur wenige Zeichen gespart. Mehr sparen kann man, wenn man meinen viel zu komplizierten Scanner durch einen Generatorausdruck ersetzt:

Code: Alles auswählen

        self.scanner = (m.group() for m in re.finditer(r"\d+|\*\*|[-+*/()]|", s))
Stefan
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

sma hat geschrieben:Ich meide diese Extra-Imports. Sie hätten nur wenige Zeichen gespart.
Darum geht es mir nicht. Ich finde, dass damit die Lesbarkeit erhöht wird. Die Länge des Quellcodes spielt eine sekundäre Rolle.
Antworten