Wo liegt der Fehler?

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.
iNoob
User
Beiträge: 10
Registriert: Sonntag 25. Juni 2023, 18:39

Vielen Dank :geek:
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Oder mal als etwas anderer Ansatz..

Code: Alles auswählen

import random

def make_formula(formula_typ = 'sum'):
    a = random.randint(1,9)
    b = random.randint(1,9)
    match formula_typ:
        case 'sum':
            return  f"{a}+{b}", a+b
        case 'product':
            return  f"{a}*{b}", a*b

print("Summen-/Produkt-Trainer (Ende mit x)")
formula_types, new = ('sum','product'), True

while  True :
    if new:
        formula, result = make_formula(random.choice(formula_types))

    answer = input(f" Wie lautet die Lösung von {formula} = ? ")

    if  answer.isnumeric() and int(answer) == result:
        print("Die Lösung ist richtig!")
        new = True
    elif answer.isnumeric() and int(answer) != result:
        print(f"Die Lösung ist falsch! Richtig ist {formula} = {result}.")
        new = True
    elif answer != 'x':
        print("Bitte nur ganze Zahlen eingeben.")
        new = False
    else:
        print("Auf Wiedersehen!")
        break
Summen-/Produkt-Trainer (Ende mit x)
Wie lautet die Lösung von 3+7 = ?
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 3+7 = ? 12
Die Lösung ist falsch! Richtig ist 3+7 = 10.
Wie lautet die Lösung von 3*5 = ? 15
Die Lösung ist richtig!
Wie lautet die Lösung von 1*2 = ? a
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 1*2 = ? 2.0
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 1*2 = ? 2
Die Lösung ist richtig!
Wie lautet die Lösung von 6+8 = ? x
Auf Wiedersehen!
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Qubit: ``match``/``case`` ist mir persönlich noch zu neu, das können noch viele LTS-Distributionen nicht mit dem standardmässig installierten Python. Und das ist IMHO auch kein sinnvoller Einsatz hier. Warum kein ``if``/``elif``/``else``? ``match`` macht erst wirklich Sinn wenn man den Mehrwert dieses Konstrukts, das strukturelle ”matchen” von Werte/Strukturen verwendet. Ein ``else`` beziehungsweise ``case _:`` fehlt mir hier auch. Die Funktion sollte nicht einfach ein implizites `None` zurück geben, sondern eine Ausnahme auslösen, oder wenigstens *explizit* `None` zurückgeben.

Ich würde da auch nicht über Zeichenketten mit besonderen Werten gehen. Man kann die Zeichenkettenvorlage beziehungsweise das Operatorsymbol und die Operation als Funktion (→ `operator`-Modul) in einem Tupel zusammenfassen und diese Werte dann direkt verwenden.

In der Hauptschleife ist `new` komisch und überflüssig. Solche Flags machen den Code IMHO unübersichtlicher als wenn man Kontrollstrukturen verwendet, wo man auch an der Einrückung die Struktur erkennen kann und nicht Flags durch das gesamte Hauptprogramm verfolgen muss um zu verstehen was wann passiert.

Die mehrfachen Tests auf `isnumeric()` und das explizite Testen von genau gegenteiligen Bedingungen in ``if``/``elif`` sollte man auch gar nicht erst anfangen. Zumal bei `isnumeric()` eventuell überrascht was da am Ende alles als Numerisch durchgeht *und* nicht alles davon ist dann auch in eine Zahl umwandelbar! Es gibt 1.205 verschiedene Zeichen bei denen `isnumeric()` wahr ist, aber `int()` einen `ValueError` auslöst. Das ist kein robuster Test an dieser Stelle. Mal das erste Beispiel wenn man nach Codepoint-Wert geht:

Code: Alles auswählen

In [126]: "²".isnumeric()
Out[126]: True

In [127]: int("²")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [127], in <cell line: 1>()
----> 1 int("²")

ValueError: invalid literal for int() with base 10: '²'
Das ist noch nicht mal wirklich exotisch, das kann ich ganz regulär über eine Taste eingeben wo das auf der Taste draufgedruckt ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

__blackjack__ hat geschrieben: Sonntag 2. Juli 2023, 22:34 @Qubit: ``match``/``case`` ist mir persönlich noch zu neu, das können noch viele LTS-Distributionen nicht mit dem standardmässig installierten Python. Und das ist IMHO auch kein sinnvoller Einsatz hier. Warum kein ``if``/``elif``/``else``? ``match`` macht erst wirklich Sinn wenn man den Mehrwert dieses Konstrukts, das strukturelle ”matchen” von Werte/Strukturen verwendet. Ein ``else`` beziehungsweise ``case _:`` fehlt mir hier auch. Die Funktion sollte nicht einfach ein implizites `None` zurück geben, sondern eine Ausnahme auslösen, oder wenigstens *explizit* `None` zurückgeben.
[...]

Code: Alles auswählen

In [126]: "²".isnumeric()
Out[126]: True

In [127]: int("²")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [127], in <cell line: 1>()
----> 1 int("²")

ValueError: invalid literal for int() with base 10: '²'
Das ist noch nicht mal wirklich exotisch, das kann ich ganz regulär über eine Taste eingeben wo das auf der Taste draufgedruckt ist.
Das sehe ich ein, guter Hinweis! :)
Dann vielleicht eher so..

Code: Alles auswählen

import random

def make_formula(formula_typ = 'sum'):
    a = random.randint(1,9)
    b = random.randint(1,9)

    return  {
        'sum'    : (f"{a}+{b}", a+b),
        'product': (f"{a}*{b}", a*b)
    }.get(formula_typ)


formula_types, new = ('sum','product'), True

print("Summen-/Produkt-Trainer (Ende mit x)")
while True:
    formula, result = make_formula(random.choice(formula_types))

    while True:
        answer = input(f" Wie lautet die Lösung von {formula} = ? ")
        if answer == 'x':
            new = False
            break
        try:
            if int(answer) == result:
                print("Die Lösung ist richtig!")
                break
        except ValueError:
            print("Bitte nur ganze Zahlen eingeben.")
            continue
        else:
            print(f"Die Lösung ist falsch! Richtig ist {formula} = {result}.")
            break

    if not new:
        print("Auf Wiedersehen!")
        break
Summen-/Produkt-Trainer (Ende mit x)
Wie lautet die Lösung von 9*6 = ?
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 9*6 = ? 70
Die Lösung ist falsch! Richtig ist 9*6 = 54.
Wie lautet die Lösung von 8+2 = ? 10
Die Lösung ist richtig!
Wie lautet die Lösung von 5*8 = ?
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 5*8 = ? a
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 5*8 = ? 40.
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 5*8 = ? 40
Die Lösung ist richtig!
Wie lautet die Lösung von 8*1 = ? x
Auf Wiedersehen!
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

oder..

Code: Alles auswählen

import random

def make_formula(formula_typ = 'sum'):
    a = random.randint(1,9)
    b = random.randint(1,9)

    return  {
        'sum'    : (f"{a}+{b}", a+b),
        'product': (f"{a}*{b}", a*b)
    }.get(formula_typ)


formula_types, new = ('sum','product'), True

print("Summen-/Produkt-Trainer (Ende mit x)")
while True:
    formula, result = make_formula(random.choice(formula_types))

    while True:
        answer = input(f" Wie lautet die Lösung von {formula} = ? ")

        if answer == 'x':
            new = False
            break

        try:
            is_ok = (int(answer) == result)
        except ValueError:
            print("Bitte nur ganze Zahlen eingeben.")
            continue
        else:
            if is_ok:
                print("Die Lösung ist richtig!")
            else:
                print(f"Die Lösung ist falsch! Richtig ist {formula} = {result}.")
            break

    if not new:
        print("Auf Wiedersehen!")
        break
Summen-/Produkt-Trainer (Ende mit x)
Wie lautet die Lösung von 3*4 = ?
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 3*4 = ? 13
Die Lösung ist falsch! Richtig ist 3*4 = 12.
Wie lautet die Lösung von 9*1 = ? 9
Die Lösung ist richtig!
Wie lautet die Lösung von 1*5 = ? 6.0
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 1*5 = ? a
Bitte nur ganze Zahlen eingeben.
Wie lautet die Lösung von 1*5 = ? x
Auf Wiedersehen!
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Oder mit einem Punktesystem..

Code: Alles auswählen

import random

def make_formula(formula_typ = 'sum'):
    a = random.randint(1,9)
    b = random.randint(1,9)

    return  {    # (formula, result, points)
        'sum'    : (f"{a}+{b}", a+b, 2),
        'product': (f"{a}*{b}", a*b, 4)
    }.get(formula_typ)


formula_types = ('sum','product')
sum_points = 0
get_points = 0
nr_calcs   = 0

print("Summen-/Produkt-Trainer (Ende mit x)")

while new:= True:
    formula, result, points = make_formula(random.choice(formula_types))

    while True:
        answer = input(f"[{get_points}P] Wie lautet die Lösung von {formula} = ? ")

        if answer == 'x':
            new = None
            break

        try:
            is_ok = (int(answer) == result)
        except ValueError:
            print("Bitte nur ganze Zahlen eingeben.")
            continue
        else:
            nr_calcs += 1
            sum_points += points
            if is_ok: # richtig -> +(volle Punktzahl)
                get_points += points
                print(f"Die Lösung ist richtig! [+{points}P]")

            else: # falsch -> -(halbe Punktzahl)
                get_points -= int(points/2)
                print(f"Die Lösung ist falsch! Richtig ist {formula} = {result}. [-{int(points/2)}P]")
            break

    if new is None:
        break

print("Auf Wiedersehen!")
print(f"Du hast {nr_calcs} Frage(n) beantwort und {get_points} von {sum_points} Punkten erhalten.")
Summen-/Produkt-Trainer (Ende mit x)
[0P] Wie lautet die Lösung von 4+4 = ? 5
Die Lösung ist falsch! Richtig ist 4+4 = 8. [-1P]
[-1P] Wie lautet die Lösung von 3*9 = ? 27
Die Lösung ist richtig! [+4P]
[3P] Wie lautet die Lösung von 5+9 = ? 14
Die Lösung ist richtig! [+2P]
[5P] Wie lautet die Lösung von 3*2 = ? x
Auf Wiedersehen!
Du hast 3 Frage(n) beantwort und 5 von 8 Punkten erhalten.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Auch mit dem Wörterbuch hat man immer noch eine Indirektion von Zeichenketten mit besonderer Bedeutung und dem Code für die Rechenart. Besser/direkter/einfacher wäre es die Rechenarten gleich als Paar von Rechensymbol und ausführbarer Operation (=Funktion) zu speichern und daraus auszuwählen.

Die erste Zuweisung an `new` finde ich zu versteckt. So Mehrfachzuweisungen würde ich eher machen wenn die Dinge etwas miteinander zu tun haben.

Wenn man die Zuweisung an `new` an den Ort verschiebt wo sie letztlich benötigt wird, sieht man relativ deutlich, dass das eigentlich in der ``while``-Bedingung verwendet werden sollte, statt das ganz am Ende der Schleife manuell zu testen. Und `new` als Name ist ja mittlerweile nicht mehr richtig.

``continue`` ist an sich schon unschön, weil das ein unbedingter Sprung ist, denn man nicht an der Einrückung erkennen kann. Und es ist problematisch wenn man Teile aus Schleifen in eigene Funktionen heraus ziehen will, oder Code am Ende *jeden* Schleifendurchlaufs hinzufügen muss. Hier ändert sich auch gar nichts am Programmablauf wenn man das einfach weg lässt.

Überarbeitet (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import random
from operator import add, mul as multiply

CALCULATION_TYPE = [("+", add), ("*", multiply)]


def make_formula(formula_type):
    symbol, operation = formula_type
    a = random.randint(1, 9)
    b = random.randint(1, 9)
    return f"{a} {symbol} {b}", operation(a, b)


def main():
    print("Summen-/Produkt-Trainer (Ende mit x)")
    is_running = True
    while is_running:
        formula, result = make_formula(random.choice(CALCULATION_TYPE))

        while True:
            answer = input(f"Wie lautet die Lösung von {formula} = ? ")

            if answer == "x":
                is_running = False
                break

            try:
                is_correct = int(answer) == result
            except ValueError:
                print("Bitte nur ganze Zahlen eingeben.")
            else:
                if is_correct:
                    print("Die Lösung ist richtig!")
                else:
                    print(
                        f"Die Lösung ist falsch!"
                        f" Richtig ist {formula} = {result}."
                    )
                break

    print("Auf Wiedersehen!")


if __name__ == "__main__":
    main()
Das ist jetzt wieder eine Umsetzung wo die Benutzereingabe samt Umwandlung nicht sauber von der Auswertung getrennt ist. Finde ich deswegen problematisch, weil sobald das etwas grösser wird und/oder man das auch so sinnvoll auf Funktionen aufteilen will, eine offensichtliche Funktion so etwas wie ``def ask_number(prompt)`` ist, und das bekommt man aus dieser Programmstruktur nicht einfach herausgezogen.

Edit: Bezog sich alles auf den vorletzten Beitrag. Jetzt zum letzen: ``while new := True:`` ist umständlich für ``while True:``. Das `new` macht da keinen Sinn.

`sum_points` und `get_points` wären Funktionsnamen. Das zweite sollte eher `total_points` heissen, das erste eher `max_points` or `max_reachable_points`.

`nr_calcs` besteht aus zwei Abkürzungen und dazu noch aus einer Deutschen und einer Englischen. `number` abgekürzt ist `no`. Und `no` ist problematisch weil es für sich selbst eine Bedeutung hat und deshalb verwirrend werden kann. `calculation_count` oder `question_count` würde ich da verwenden.

Statt ``int(points/ 2)`` könnte man auch ``points // 2`` schreiben.

Die Punkte pro Aufgabentyp liessen sich in die Tupel aus der Lösung weiter oben einfach einbauen. So anonyme Tupel mit drei Werten sind dann aber für meinen Geschmack auch langsam die Grenze wo man besser auf `collections.namedtuple()` oder eine eigene Klasse umsteigt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

__blackjack__ hat geschrieben: Montag 3. Juli 2023, 10:44 Edit: Bezog sich alles auf den vorletzten Beitrag. Jetzt zum letzen: ``while new := True:`` ist umständlich für ``while True:``. Das `new` macht da keinen Sinn.

`sum_points` und `get_points` wären Funktionsnamen. Das zweite sollte eher `total_points` heissen, das erste eher `max_points` or `max_reachable_points`.

`nr_calcs` besteht aus zwei Abkürzungen und dazu noch aus einer Deutschen und einer Englischen. `number` abgekürzt ist `no`. Und `no` ist problematisch weil es für sich selbst eine Bedeutung hat und deshalb verwirrend werden kann. `calculation_count` oder `question_count` würde ich da verwenden.

Statt ``int(points/ 2)`` könnte man auch ``points // 2`` schreiben.

Die Punkte pro Aufgabentyp liessen sich in die Tupel aus der Lösung weiter oben einfach einbauen. So anonyme Tupel mit drei Werten sind dann aber für meinen Geschmack auch langsam die Grenze wo man besser auf `collections.namedtuple()` oder eine eigene Klasse umsteigt.
Teilweise gebe ich dir Recht, zum anderen Teil scheint es mir aber jetzt nach einer Betrachtung nach Gusto. :)
Es geht mir dabei auch nicht um ein "perfektes Programm", sondern wie man es strukturieren könnte, um es leicht erweitern zu können. So kann man "make_formula" nach belieben abstrahieren, aber man sollte die Erstellung der Formeln/Aufgaben vom eigentlichen Programmfluss (Eingabe/Prüfung/Ausgabe/Punkte/..) trennen. Du bist ja offenbar auch ein Freund funktionalen Programmierens?.

Das "while new:= True"-Konstrukt mag hier ungewohnt sein, aber es verschlechtert m.E. nichts, es kostet weder mehr Speicher noch Performance.
Es geht mehr hier eher darum, die entsprechenden Kontexte auf gleicher Ebene zu haben, du beendest das Programm zB. in deinem Beispielcode direkt aus der inneren while-Schleife heraus.
Das finde ich etwas "krude", wenn man nicht Logik der äusseren Schleife in die innere verlegen will (sie nächsten Beispielcode).

Auch mit dem "continue" der inneren Schleife hast du an sich Recht, es ist hier redundant. Aber ich lasse es trotzdem erstmal stehen (solange das Programm nicht fertig ist), um es bei ggfs. Codeerweiterungen im Auge zu haben, so im weiteren Beispiel als Kommentar..

Eine kleine Erweiterung (mit paar Umsetzen deiner Vorschläge)..

Code: Alles auswählen

# -*- coding: utf-8 -*-
import random
import math

def make_formula(formula_type = 'sum'):
    a = random.randint(1,9)
    b = random.randint(1,9)
    c = random.randint(1,9)

    return  {       # (formula, result, points)
        'sum'       : (f"{a}+{b}", a+b, 2),
        'product'   : (f"{a}*{b}", a*b, 4),
        'squareroot': (f"√({a**2})",int(math.sqrt(a**2)), 6),
        'mixed'     : (f"{a}+{b}*{c}",a+b*c, 6)
    }.get(formula_type)

def formel_trainer(*types):
    formula_types = (types) if types else ('sum',)
    max_points = 0
    total_points = 0
    number_of_calcs = 0
    min_number_of_calcs = 5

    operations_de = {'sum': 'Summen', 'product': 'Produkte', 'squareroot': 'Wurzel'}
    opertion_names = '/'.join(map(lambda  x: operations_de.get(x,x),formula_types))
    
    print(f"{opertion_names}-Trainer (Ende mit x)")
    
    while new:= True:
        formula, result, points = make_formula(random.choice(formula_types))

        retry_bad_answers = 3
        while True:
            answer = input(f"[{total_points}P] Wie lautet die Lösung von {formula} = ? ")

            if answer == 'x':
                new = None
                break

            try:
                is_ok = (int(answer) == result)
            except ValueError:
                print("FEHLER: Bitte nur ganze Zahlen eingeben.")
                retry_bad_answers -= 1
                if retry_bad_answers == 0:
                    print("FEHLER: 3 falsche Eingaben. Neue Aufgabe..")
                    break
                #continue
            else:
                number_of_calcs += 1
                max_points += points
                
                if is_ok: # richtig -> +(volle Punktzahl)
                    total_points += points
                    print(f"Die Lösung ist richtig! [+{points}P]")
                else: # falsch -> -(halbe Punktzahl)
                    total_points -= points//2
                    print(f"Die Lösung ist falsch! Richtig ist {formula} = {result}. [-{points//2}P]")
                break

        if new is None:
            if number_of_calcs >= min_number_of_calcs:
                break
            else:
                print(f"Du sollst mindestens {min_number_of_calcs} Aufgaben rechnen! {number_of_calcs} Aufgabe(n) hast Du bisher gerechnet.")

    print("Auf Wiedersehen!")
    print(f"Du hast {number_of_calcs} Frage(n) beantwort und {total_points} von {max_points} Punkten erhalten.")


if __name__=='__main__':
    formel_trainer('sum','product','squareroot','mixed')
Summen/Produkte/Wurzel/mixed-Trainer (Ende mit x)
[0P] Wie lautet die Lösung von √(9) = ? 3
Die Lösung ist richtig! [+6P]
[6P] Wie lautet die Lösung von 3*5 = ? 234
Die Lösung ist falsch! Richtig ist 3*5 = 15. [-2P]
[4P] Wie lautet die Lösung von 9+7*2 = ? x
Du sollst mindestens 5 Aufgaben rechnen! 2 Aufgabe(n) hast Du bisher gerechnet.
[4P] Wie lautet die Lösung von √(64) = ?
FEHLER: Bitte nur ganze Zahlen eingeben.
[4P] Wie lautet die Lösung von √(64) = ? 5
Die Lösung ist falsch! Richtig ist √(64) = 8. [-3P]
[1P] Wie lautet die Lösung von 6*7 = ? 42
Die Lösung ist richtig! [+4P]
[5P] Wie lautet die Lösung von 1*7 = ? x
Du sollst mindestens 5 Aufgaben rechnen! 4 Aufgabe(n) hast Du bisher gerechnet.
[5P] Wie lautet die Lösung von 4*4 = ? 16
Die Lösung ist richtig! [+4P]
[9P] Wie lautet die Lösung von 1+3 = ? x
Auf Wiedersehen!
Du hast 5 Frage(n) beantwort und 9 von 24 Punkten erhalten.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Qubit: Ja ich mag auch funktionale Programmierung ganz gerne.

Ich weiss nicht ob ich `make_formula()` mehr abstrahiert habe, es ist halt einfacher geworden ohne den zusätzlichen Zwischenschritt über Zeichenketten. Man kann sich nicht mehr vertippen, muss sich also nicht um Fehler in der Schreibweise kümmern, und man muss die gleichen Zeichenketten nicht zweimal im Programm stehen haben.

Das ``while new := True:`` verschlechtert die Lesbarkeit unnötig. Man muss einen Augenblick länger überlegen was da passiert und dass das eine bedingungslose Schleife ist, und da zwei Sachen vermischt wurden nur um eine Zeile zu sparen. Du sagst ja selbst es ist ungewohnt. Das an sich spricht ja schon mal dagegen wenn es keinen guten Grund dafür gibt, der das ausgleicht.

Was ich da beim vorherigen vergessen hatte anzusprechen: `new` nimmt genau zwei Werte an: `True` und `None`. Das sollte eher `True` und `False` sein, sonst ist das komisch solange man keine dreiwertige Logik bauen will mit `True`, `False` und `None`. `None` für „unbekannt“ beispielsweise.

Ja das ``return`` um aus der inneren Schleife zu kommen ist nicht schön. Das geht ja auch nicht wenn man die Eingabe in eine eigene Funktion heraus zieht, was man eigentlich machen sollte. Da hatte ich dann ja auch eine Variante wo es eine extra Antwort gab, die entweder eine Zahl oder `None` ist, was dann in der äusseren Schleife als Abbruchbedingung verwendet werden kann. Bei der Variante kann man die Benutzereingabe sehr leicht in eine eigene Funktion herausziehen. Da ist auch Eingabe + Umwandlung und Auswertung der Antwort genau aus dem Grund an genau dieser Linie getrennt.

``continue`` ist wie ``global``. Das muss *immer weg*. Das lässt man schon gar nicht sinnlos auf Vorrat irgendwo drin. Man übersieht es leicht, und es macht wie gesagt Probleme beim überarbeiten und erweitern von Code. Man kann nichts aus einer Schleife einfach in eine eigene Funktion herausziehen wenn da ein ``continue`` drin ist, weil das in der Funktion dann ohne die Schleife so nicht funktioniert. Und wenn man am Ende einer Schleife etwas hinzufügen will was grundsätzlich ausgeführt werden soll, hat man das Problem, dass jedes ``continue`` das umgeht und man den zusätzlichen Code in eine eigene Funktion schreiben und die dann am Ende der Schleife und vor jedem ``continue`` aufrufen. Das macht Code in der Regel unübersichtlicher. Oder man strukturiert das ohne ``continue`` um, macht also Arbeit die ursprünglich *gleich* hätte gemacht werden können/sollen, statt des unsäglichen ``continue``. ``continue`` ist die kleine Schwester von ``sys.exit()`` — oft ein Hack um sich keine Gedanken um einen saubere(re)n Programmablauf machen zu müssen.

Zum neuen Quelltext: Bei `formel_trainer()` ist die *-Magie in der Signatur unschön. Man verbaut sich damit unnötig weitere Positionsargumente anfügen zu können um sich beim Aufrufer die Klammern für eine Liste zu sparen. Und falls man die Argumente beim Aufruf nicht hart kodieren, sondern beispielsweise vorher auswählbar machen will, braucht man plötzlich auch beim Aufruf *-Magie.

Bei den vielen Informationen die mittlerweile zu einem Aufgabentyp gehören und über verschiedene Datenstrukturen über das Programm verteilt sind, ist für mich definitiv der Punkt erreicht wo man das in einer Klasse zusammenfassen sollte. Wenn man da eine weitere Rechenart hinzufügen möchte, muss man das an zu vielen Stellen im Programm verteilt machen. Und das unfallfrei.

Die Funktion ist auch ziemlich lang und verschachtelt und mit 15 lokalen Namen die fast alle eine Bedeutung haben die mehr als nur lokal sehr begrenzter Zwischenwert ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Umsetzung des letzten Ablaufs mit Mindestanzahl von Antworten und Begrenzung der Falscheingaben, mit Klassen, allerdings immer noch funktional, und die ganze Komlexität auf (hoffentlich) verdaulichere kleinere Funktionen aufgeteilt:

Code: Alles auswählen

#!/usr/bin/env python3
import random
from operator import add, mul as multiply

from attr import attrib, attrs

END_INPUT = "x"
RETRY_COUNT = 3
MIN_ANSWERED_QUESTIONS_COUNT = 5


class RetryCountExceeded(Exception):
    pass


@attrs(frozen=True)
class Points:
    value = attrib(default=0)
    max_value = attrib(default=0)

    def __str__(self):
        return f"{self.value} von {self.max_value}"

    def __add__(self, other):
        return Points(
            self.value + other.value, self.max_value + other.max_value
        )


@attrs(frozen=True)
class Question:
    text = attrib()
    answer = attrib()
    points = attrib()


@attrs(frozen=True)
class CalculationType:
    name = attrib()
    template = attrib()
    calculate = attrib()
    points = attrib()
    prepare = attrib(default=lambda *operands: operands)

    def create_question(self):
        operands = [
            random.randint(1, 9) for _ in range(self.template.count("{}"))
        ]
        return Question(
            self.template.format(*self.prepare(*operands)),
            self.calculate(*operands),
            self.points,
        )


CALCULATION_TYPES = [
    CalculationType("Summen", "{} + {}", add, 2),
    CalculationType("Produkte", "{} × {}", multiply, 4),
    CalculationType("Wurzeln", "√({})", lambda a: a, 6, lambda a: [a**2]),
    CalculationType("Mixed", "{} + {} × {}", lambda a, b, c: a + b * c, 6),
]


def ask_result(prompt):
    for _ in range(RETRY_COUNT):
        answer = input(prompt)
        if answer == END_INPUT:
            raise KeyboardInterrupt()

        try:
            return int(answer)
        except ValueError:
            print("FEHLER: Bitte nur ganze Zahlen eingeben.")

    raise RetryCountExceeded()


def ask_question(total_points):
    question = random.choice(CALCULATION_TYPES).create_question()
    assert question.answer != END_INPUT

    try:
        result = ask_result(
            f"[{total_points.value}P]"
            f" Wie lautet die Lösung von {question.text} = "
        )
    except RetryCountExceeded:
        print(f"FEHLER: {RETRY_COUNT} falsche Eingaben.  Neue Aufgabe…")
        return None
 
    else:
        if result == question.answer:
            points = question.points
            text = "Die Lösung ist richtig!"
        else:
            points = -(question.points // 2)
            text = (
                f"Die Lösung ist falsch!"
                f"  Richtig ist {question.text} = {question.answer}."
            )

        print(f"{text} [{'' if points < 0 else '+'}{points}P]")
        return Points(points, question.points)


def ask_questions():
    answered_questions_count = 0
    total_points = Points()
    while True:
        try:
            points = ask_question(total_points)
            if points is not None:
                answered_questions_count += 1
                total_points += points

        except KeyboardInterrupt:
            if answered_questions_count >= MIN_ANSWERED_QUESTIONS_COUNT:
                break

            print(
                f"Du musst mindestens {MIN_ANSWERED_QUESTIONS_COUNT}"
                f" Aufgaben rechnen!  {answered_questions_count} Aufgabe(n)"
                f" hast Du bisher gerechnet."
            )

    return answered_questions_count, total_points


def main():
    operation_names = "/".join(
        calculation_type.name for calculation_type in CALCULATION_TYPES
    )
    print(f"{operation_names}-Trainer (Ende mit {END_INPUT!r})")

    answered_questions_count, points = ask_questions()

    print("Auf Wiedersehen!")
    print(
        f"Du hast {answered_questions_count} Frage(n) beantwortet und"
        f" {points} Punkten erhalten."
    )


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten