Kopfrechnungs-Simulator

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Procl
User
Beiträge: 2
Registriert: Dienstag 19. Juni 2018, 22:57

Hi Leute

Bin neu hier im Forum sowie auch in Python.

Hab mir als kleine Aufgabe ein Kopfrechnungs-Simulator programmiert, denn ein Taschenrechner habe ich schon^^.

Ich stehe noch ziemlich am Anfang und wollte mal Eure Meinung ggf. Verbesserungsvorschläge/Änderungen hören :)
Kommentare wurden NOCH keine hinzugefügt - Werde ich aufjedenfall bei meinen nächsten "Übungs-Programmen" dazufügen!

Code: Alles auswählen

def kopfrechnen():
    from random import randint

    def intro():
        print("Willkommen!\n")
        print("Das ist der Kopfrechnungs-Simulator-1337")
        print("Hier können Sie ihre Kopfrechnungs-Skills verbessern")
        print("Gerechnet kann mit allen gängigen Operatoren\n")
        print("+, -, /, *\n")
        print("Haben Sie alles Verstanden?\nDann kann es endlich Losgehen!\n")


    def auswahl():
        global punkte
        punkte = []
        global usr_op_wahl
        operatoren_liste = {1: "Addition", 2: "Subbtraktion", 3: "Division", 4: "Multiplikation"}
        print("Auswahlmöglichkeiten:")
        for i in operatoren_liste:
            print("{zahl}: {operand}".format(zahl=i, operand=operatoren_liste[i]))
        usr_op_wahl = int(input("\nAuswahl mittels Zahl (1-4) Eingeben: "))

        if usr_op_wahl == 1 or usr_op_wahl == 2 or usr_op_wahl == 3 or usr_op_wahl == 4 in operatoren_liste:
            print("Sie haben sich für {} Entschieden".format(operatoren_liste[usr_op_wahl]))

        else:
            print("Bitte richtige Zahl eingeben")

    def durchgänge():
        global anzahl_durchgänge
        print("Wie viele Rechnungen wollen Sie pro Durchlauf ausführen?")
        anzahl_durchgänge = int(input("Anzahl Durchgänge eingeben: "))


    def rechnen():
        global ergebniss, usr_ergebniss, rechnung
        z1 = randint(1, 11)
        z2 = randint(1, 11)
        operatoren = {1: "+", 2: "-", 3: "/", 4: "*"}
        if usr_op_wahl == 1 or usr_op_wahl == 2 or usr_op_wahl == 3 or usr_op_wahl == 4 in operatoren:

            ergebniss = eval("{} {} {}".format(z1, operatoren[usr_op_wahl], z2))
            usr_ergebniss = float(input("{} {} {} = ".format(z1, operatoren[usr_op_wahl], z2)))
            rechnung = "{} {} {} = {}".format(z1, operatoren[usr_op_wahl], z2, ergebniss)

    def score():

        if ergebniss == usr_ergebniss:
            print("Richtig! -> {}\n".format(rechnung))
            punkte.append(1)
        else:
            print("Leider Falsch! -> Richtiges Ergebniss: {}".format(ergebniss))
            punkte.append(0)

    def again():
        print("\n{} von {} Rechnungen wurden richtig gelöst".format(punkte.count(1), len(punkte)))
        print("\nWollen Sie erneut Ihr Glück auf die Probe stellen?")
        antwort = {1: "Ja", 2: "Nein"}
        for i in antwort:
            print("{zahl}: {antwort}".format(zahl=i, antwort=antwort[i]))
        usr_again_wahl = int(input("\nBitte Wahl treffen"))
        if usr_again_wahl == 1:
            alles()
        elif usr_again_wahl == 2:
            exit()
        else:
            print("\nBitte Zahl 1 oder Zahl 2 eingeben: ")




    def alles():

        auswahl()
        durchgänge()
        for i in range(1, anzahl_durchgänge+1):
            rechnen()
            score()
        again()



    intro()
    alles()
kopfrechnen()
grüsse
Procl
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

Eine Funktion zu definieren, die selbst Funktionen enthält ist an dieser Stelle nicht das, was du eigentlich machen willst. Entweder du erstellst eine Klasse `Kopfrechnen` welche die entsprechenden Methoden enthält, oder du hältst dein Programm weiterhin rein funktional und definierst statt `kopfrechnen()` eine main()-Methode die als Einstiegspunkt für dein Programm steht und Benutzereingaben entgegen nimmt, auswertet und anschließend die Funktionen aufruft.

Vergiss dass es global gibt, das zu verwenden ist in den aller meisten fällen schlicht falsch, es macht den Zustand deines Programmes unübersichtlich und kann mitunter zu extrem hässlichen Fehlern führen.

Umlaute in Funktionsbezeichnungen sind nicht schoen :) Generell solltest du Englisch und Deutsch in einem Programm nicht mischen, das war noch nie eine gute Idee. (auswahl, score, again...)

Was macht `alles()` ? Da brauche ich mir keine Docstrings durchlesen, der Name der Funktion ist einfach schlecht. Der Name sollte so gewählt werden, dass man *weiß* was die Funktion macht, ohne raten oder debuggen oder Doku lesen zu müssen.

Wo ist die Funktion `exit()` definiert? Sie wird verwendet, steht aber nirgends im Code noch wird sie von irgendwo importiert. Eingerückt wird immer mit 4 Leerzeichen, teilweise hast du deutlich zu viele Zeilenumbrüche.

`z1` und `z2` sind wie oben schon erläutert ebenso extrem schlechte Variablenbezeichner. Z1, meinst du den mechanischen Rechner von Zuse? Achso, klar, den kennt man ja ;)

Die Bedingung "if usr_op_wahl == 1 or usr_op_wahl == 2 or usr_op_wahl == 3 or usr_op_wahl == 4 in operatoren_liste:" ist syntaktisch nicht schön, das kann man übersichtlicher mit einem "elif" abwickeln. Was willst du hier außerdem auswerten? Eine Entscheidung hat der Benutzer getroffen, also lass doch die if-Bedingung weg und gib' einfach aus wofür er sich entschieden hat.

Was, wenn der Benutzer nicht alles verstanden hat ? (siehe letzten print()-Aufruf in intro()) -> du könntest hier genauso gut schreiben, "haben sie alles verstanden? nein? dann sterben Sie jetzt dumm!", oder du bietest hier eine hilfe-option an. Das aber nur am Rande.

Generell kann man den Code stark komprimieren und die Logik etwas einschrumpfen. Aber erstmal eines nach dem anderen.
When we say computer, we mean the electronic computer.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Willkommen im Forum und nicht erschrecken, wenn jetzt viel Kritik kommt, viele Dinge lernt man erst mit der Zeit und Erfahrung.

Alles in eine Funktion zu packen hat keine Vorteile. Verschachtelte Funktionen sollte man vermeiden, weil man die inneren Funktionen nicht einzeln testen kann.
Importe sollten immer auf oberster Ebene ganz am Anfang der Datei stehen, so dass man sofort alle Abhängigkeiten erkennen kann.
Vergiß dass es `global` gibt. Funktionen haben immer eine bestimmte Funktion, bekommen das, was sie zum Bearbeiten ihrer Aufgabe brauchen über Parameter und liefern das Ergebnis als Rückgabewert zurück. `auswahl` hat als Ergebnis `usr_op_wahl`, die Punkte auf eine leere Liste zu setzen ist aber nicht die Aufgabe, das sollte auf einer anderen Ebene passieren.

Wörterbücher sind nicht notwendigerweise sortiert. Die Ausgabe kann also theoretisch die Reihenfolge 3. 4. 2. 1. haben.
Die if-Abfrage `usr_op_wahl == 4 in operatoren_liste` macht nicht das was Du denkst, bzw. ich erkenne nicht, was Du damit ausdrücken willst. Mehrere Vergleichsoperatoren werden in Python per `and` verknüpft, die Abfrage lautet also `usr_op_wahl == 4 and 4 in operatoren_liste`, es wird also geprüft ob 4 in operatoren_liste ist, und das ist immer wahr.
Was Du wahrscheinlich wolltest ist die Kurzform `if usr_op_wahl in operatoren_liste:`.
Im else-Teil mit "Bitte richtige Zahl eingeben" machst Du einfach mit der falschen Eingabe weiter, was später zu Problemen führt.

Niemals `eval` benutzen, für die Rechenoperationen gibt es das operator-Modul.

In `alles` und `again` benutzt Du Rekursion um das Spiel zu wiederholen. Hier ist aber Rekursion das falsche Mittel. Statt dessen solltest Du eine Endlos-Schleife benutzen, die Du im `Nein`-Fall per `break` verlassen kannst. Apropos `verlassen`. Es sollte nicht davon ausgegangen werden dass `sys.exit` implizit als `exit` im Namensraum existiert. `exit` sollte auch in einem normalen Programm nicht benutzt werden, sondern man sollte dafür sorgen, dass die Haupt-Funktion normal verlassen wird und so das Programm ein natürliches Ende findet.

Was Du also als nächstes Lernen könntest, wäre richtige Funktionen zu schreiben.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das die Reihenfolge bei Wörterbüchern nicht unbedingt der Reihenfolge entspricht wie man die Elemente in den Quelltext schreibt, hatte Sirius3 ja schon erwähnt. In allen Fällen wo Du ein literales Wörterbuch im Quelltext stehen hast, sind die Schlüssel einfach von 1 aufsteigend nummeriert — das ist im Grunde redundante Information, denn wenn man einfach eine Liste schreiben würde, kann man sich dieser Zahlen mit `enumerate()` für die Schleife über die Elemente generieren.

In `auswahl()` hast Du das ja sogar `operatoren_liste` genannt obwohl es ein Wörterbuch und keine Liste ist. Es sollte aber eine Liste sein. Allerdings dann ohne den Zusatz `liste` im Namen, denn Grunddatentypen haben in Namen nichts zu suchen. Das ändert sich im Laufe der Programmentwicklung oft, das man mit einem einfachen Grunddatentyp anfängt und den später durch einen anderen ersetzt, und dann muss man entweder alle Namen dafür anpassen, oder man hat irreführende Namen im Programm stehen. Wie `operatoren_liste` für ein Wörterbuch. :-) In diesem Falle wäre auch `operatoren` falsch, denn gespeichert sind die Rechenoperationen und nicht die Rechenoperatoren. Die Operation in einem Platzhalter dann `operand` zu nennen ist noch mehr daneben. Gute Namen sind wichtig.

Subtraktion schreibt man übrigens nur mit einem b.

`operatoren` in `auswahl()` und in `rechnen()` sind ”parallele” Datenstrukturen. In beiden werden die gleichen Zahlen jeweils auf Namen von Rechenoperationen und die entsprechenden Symbole abgebildet. Die Namen und die Symbole gehören aber zusammen. Und wenn man `eval()` aus dem Programm raus wirft, dann kommt auch noch eine Funktion hinzu, welche die Rechenoperation dann auch tatsächlich ausführt. Das könnte man als Liste von Tupeln modellieren, oder als Liste von Wörterbüchern, oder noch besser `collections.namedtupel`. Etwas in der Art:

Code: Alles auswählen

#!/usr/bin/env python3
from collections import namedtupel
import operator

Operation = namedtupel('Operation', 'name symbol function')

OPERATIONS = [
    Operation('Addition', '+', operator.add),
    Operation('Subtraktion', '-', operator.sub),
    Operation('Multiplikation', '*', operator.mul),
    Operation('Division', '/', operator.div),
]
Wenn Du Funktionen beherrschst und zu objektorientierter Programmierung weiter gehst, kann man aus `Operation` auch eine eigene Klasse mit entsprechenden Methoden machen um beispielsweise aus zwei Operanden die Zeichenkette mit der Rechnung zu machen.

Das Punkte als Liste von 0en und 1en geführt wird, ist unnötig kompliziert. Man hat ja `anzahl_durchgaenge`, also braucht man die Länge der Punkteliste nicht, und damit ist diese Liste auch nicht nötig, sondern man kann für jede richtige Antwort einfach einen Punkt auf einen Punktezähler addieren.

Schrob ich schon das Namen wichtig sind? Der Programmname Kopfrechnungs-Simulator ist falsch weil da nirgends Kopfrechnen simuliert wird. ;-)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Procl
User
Beiträge: 2
Registriert: Dienstag 19. Juni 2018, 22:57

Erstmal: Vielen Dank, dass ihr euch die Zeit nehmt um mir zu helfen!

Hab mir euchere Kritik genaustens Durchgelesen und versuche nun das "Programm" mit eucherem Input umzuschreiben!

grüsse
Procl
Antworten