Zeitmessung für einen Rechentrainer

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
PatrickF
User
Beiträge: 27
Registriert: Sonntag 1. Mai 2022, 09:43

Hallo zusammen,
habe ein kleines Projekt angefangen, was ich nach und nach ausbauen möchte, einen individualisierbaren Rechentrainer.

Aktueller Stand ist so:

Code: Alles auswählen

import random
from time import time
zaehler=0
durchgang=int(input("Wie viele Durchgänge? "))
for i in range(durchgang):
    
    zahl1=random.randint(1,10)
    zahl2=random.randint(1,10)
    zahl3=random.randint(1,10)
    zahl4=random.randint(1,10)
    global_time=0
    start_time=time()
    raten_1=int(input(f"Wieviel ist {zahl1} * {zahl2} + {zahl3} - {zahl4}? "))
    elapsed_time=time()-start_time
    loesung=zahl1*zahl2+zahl3-zahl4
    
    if raten_1!=loesung:
        print(f"Falsch! Richtig ist {loesung}.")
        
    else:
        print(f"Richtig! {elapsed_time:.2f} Sekunden" )        
        zaehler+=1
        global_time+=elapsed_time

print(f"{zaehler} Punkte! Du hast {zaehler} von {durchgang} Aufgaben richtig gerechnet!")
print(f"Das sind {round(zaehler/durchgang*100)} Prozent!")
print(f"Gesamtzeit für {durchgang} Aufgaben: {global_time:.2f}")
Die Rechenoperationen möchte ich nach und nach natürlich mit Variablen gestalten, aktuell habe ich aber Probleme mit der globalen Zeitmessung.
global_time+=elapsed_time
zeigt mir immer nur den Wert der letzten Rechnung. Ich finde den Fehler einfach nicht.

Viele Grüße
Pf
Ernie1412
User
Beiträge: 161
Registriert: Freitag 10. Januar 2020, 20:38

in der schleife setzt du den "global_time=0" wieder auf 0
den musste nur ausserhalb der schleife setzen.
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PatrickF: Die Gesamtzeit wird nicht nur an der falschen Stelle initialisiert, sondern auch an der falschen Stelle aufaddiert, denn so zählst Du im Moment die falsch beantworteten Fragen gar nicht mit.

Sonstige Anmerkungen: `durchgang` steht gar nicht für einen Durchgang, sondern für die Anzahl der Aufgaben, sollte also besser `aufgabenanzahl` heissen.

`zaehler` ist sehr generisch, da wäre `punkte` besser/informativer.

`i` wird nicht verwendet, da ist der Name `_` üblich, damit sich der Leser nicht wundert warum ein Name nicht verwendet wird.

`global_time` müsst eher `total_time` heissen.

Die `time()`-Funktion ist Zeitmessungen nicht geeignet, weil die nicht immer vorwärts geht. Dafür gibt es `time.monotonic()` (und für Zeitmessungen wo die höchste Auflösung benötigt wird, die das System anbietet `time.perf_counter()`, aber Du würdest das ja eh wegrunden bei der Anzeige.)

Man nummeriert keine Namen durch. Entweder will man sich dann bessere Namen überlegen, oder eine Datenstruktur statt einzelner Namen/Werte. Bei `zahl1` bis `zahl4` bietet sich eine Liste mit vier Zahlen an, und bei `raten_1` macht diese 1 überhaupt keinen Sinn. Und `raten` auch nicht. Der Wert ist die Antwort des Benutzers, also `antwort`. Und die ist hoffentlich nicht geraten, denn die lässt sich ja ausrechnen. 😉

`round()` verwendet man nicht für die Ausgabe. An anderer Stelle verwendest Du ja bereits die Format-Syntax für die Anzahl der Nachkommastellen. Zudem gibt es da auch etwas für Prozente.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import random
from time import monotonic


def main():
    aufgabenanzahl = int(input("Wie viele Durchgänge? "))
    punkte = 0
    total_time = 0
    for _ in range(aufgabenanzahl):
        operanden = [random.randint(1, 10) for _ in range(4)]
        start_time = monotonic()
        antwort = int(
            input("Wieviel ist {} * {} + {} - {}? ".format(*operanden))
        )
        elapsed_time = monotonic() - start_time
        total_time += elapsed_time
        loesung = operanden[0] * operanden[1] + operanden[2] - operanden[3]
        if antwort != loesung:
            print(f"Falsch! Richtig ist {loesung}.")
        else:
            print(f"Richtig! {elapsed_time:.2f} Sekunden")
            punkte += 1

    print(
        f"{punkte} Punkte! Du hast {punkte} von {aufgabenanzahl} Aufgaben"
        f" richtig gerechnet!"
    )
    print(f"Das sind {punkte / aufgabenanzahl:.0%}!")
    print(f"Gesamtzeit für {aufgabenanzahl} Aufgaben: {total_time:.2f}s")


if __name__ == "__main__":
    main()
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
PatrickF
User
Beiträge: 27
Registriert: Sonntag 1. Mai 2022, 09:43

Ernie1412 hat geschrieben: Sonntag 29. Mai 2022, 16:30 in der schleife setzt du den "global_time=0" wieder auf 0
den musste nur ausserhalb der schleife setzen.
:lol: Ja, absolut Quatsch, fällt mir jetzt auch auf :roll:
PatrickF
User
Beiträge: 27
Registriert: Sonntag 1. Mai 2022, 09:43

__blackjack__ hat geschrieben: Sonntag 29. Mai 2022, 17:23 ...

Man nummeriert keine Namen durch. Entweder will man sich dann bessere Namen überlegen, oder eine Datenstruktur statt einzelner Namen/Werte. Bei `zahl1` bis `zahl4` bietet sich eine Liste mit vier Zahlen an, und bei `raten_1` macht diese 1 überhaupt keinen Sinn. Und `raten` auch nicht. Der Wert ist die Antwort des Benutzers, also `antwort`. Und die ist hoffentlich nicht geraten, denn die lässt sich ja ausrechnen. 😉

...
Danke für die Hinweise. Dass mit der Zeitmessung bei falscher Antwort war mir auch schon aufgefallen.

Die Nummerierung gefällt mir auch nicht. Die Operatoren plane ich auch in einer Liste zu erfassen (so in der Art: ops = ['+', '-', '*', '/']), aber da muss ich wohl noch mehr recherchieren, wie ich dann von den Strings wieder zu den Zahlen komme.

Das "raten_1" erklärt sich daraus dass es noch ein "raten_2" etc. geben soll(te), dann halt mit anderen Operatoren, per Menü wählbar. Ich merke aber schon dass das letztlich viel zu komplex wird wenn man das nicht per Funktionen modularisiert.
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PatrickF: Im `operator`-Modul gibt es für jeden Operator in Python eine Funktion die das gleiche macht. Man kann also Zeichen wie "+" zum Beispiel mit einem Wörterbuch auf diese Funktionen abbilden. Und dann könnte man sich die Aufgabe als Datenstruktur erstellen und Funktionen schreiben, die daraus eine Ausgabe machen, und eine, die so eine Struktur ausrechnet.

Nicht so tolles Beispiel:

Code: Alles auswählen

#!/usr/bin/env python3
from operator import add, mul, sub, truediv
from random import randint
from collections import namedtuple

SYMBOL_TO_FUNCTION = {"*": mul, "+": add, "-": sub, "/": truediv}

Expression = namedtuple("Expression", "symbol operand_a operand_b")


def create_expression(symbols):
    result = randint(1, 10)
    for symbol in symbols:
        result = Expression(symbol, result, randint(1, 10))
    return result


def expression_as_string(expression):
    if isinstance(expression, Expression):
        return (
            f"{expression_as_string(expression.operand_a)}"
            f" {expression.symbol}"
            f" {expression_as_string(expression.operand_b)}"
        )
    else:
        return str(expression)


def evaluate_expression(expression):
    if isinstance(expression, Expression):
        return SYMBOL_TO_FUNCTION[expression.symbol](
            evaluate_expression(expression.operand_a),
            evaluate_expression(expression.operand_b),
        )
    else:
        return expression


def main():
    expression = create_expression("*+-")
    print(expression)
    print(
        expression_as_string(expression), "=", evaluate_expression(expression)
    )


if __name__ == "__main__":
    main()
Problematisch ist hier das keine Punkt- vor Strich-Regeln berücksichtigt werden, also alles immer unabhängig von den Rechenoperationen von links nach rechts ausgewertet wird, ohne das bei der Ausgabe dafür eventuell notwendige Klammern angezeigt werden. Mindestens die Ausgabe von Klammern, dort wo das nötig ist, müsste man also noch ergänzen. Wenn man das hätte, könnte man auch tatsächlich zufällige Ausdrücke erzeugen, also wo auch die Rechenoperation(en) zufällig gewählt werden. Da müsste man dann die Division besonders behandeln, falls man sicherstellen möchte, dass durchgehend mit ganzen Zahlen gerechnet werden soll.

Unschön ist auch `isinstance()`. Da würde man eher eine objektorientierte Lösung schreiben, wo diese Entscheidung nicht mehr notwendig ist. In dem man beispielsweise eine `Value`- und eine `BinaryOperator`-Klasse schreibt, die beide die gleiche Schnittstelle anbieten.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
PatrickF
User
Beiträge: 27
Registriert: Sonntag 1. Mai 2022, 09:43

Vielen Dank für die Hinweise, ich versuche das mal so einzubauen.
Antworten