Geldautomat

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
Feedback1000
User
Beiträge: 88
Registriert: Dienstag 20. September 2022, 21:21

Hallo, ich komme nicht weiter:
Hier soll die minimale Anzahl an Münzen für einen eingegebenen Betrag ausgegeben werden.
Habe aber gerade ein Brett vorm Kopf. Könnt ihr es bitte wegnehmen?

Code: Alles auswählen

MUENZTYPEN = [
    0.01,
    0.02,
    0.05,
    0.10,
    0.20,
    0.50,
    1.00,
    2.00
]

MUENZTYPEN.sort(reverse = True)
print(MUENZTYPEN)

betrag = float(input('Den Betrag eingeben: '))
# print(betrag)

for muenze in MUENZTYPEN:
    anzahl_muenzen = 0
    while betrag >= muenze:
        anzahl_muenzen += muenze
        betrag -= muenze
    print(str(muenze) + 'er : ' + str(anzahl_muenzen))
Danke.
narpfel
User
Beiträge: 691
Registriert: Freitag 20. Oktober 2017, 16:10

Guck dir mal den Wert und Typ von `anzahl_muenzen` in jeder Iteration der `while`-Schleife an.

Dein Code hat noch ein weiteres (nicht sofort offensichtliches) Problem: `float` hat keine unendlich große Genauigkeit, sondern kann viele Werte nur gerundet widergeben. Beispiel:

Code: Alles auswählen

>>> x = 0.1
>>> count = 0
>>> while x > 0:
...     x -= 0.01
...     count += 1
...
>>> x
-0.00999999999999999
>>> count
11
Der Code teilt 10 Cent in 1-Cent-Münzen auf, aber weil `sum([0.01] * 10) < 0.1` ist, bekommt man 11 Münzen.

Und dann noch zwei allgemeine Anmerkungen: Um Strings zu formatieren, benutzt man f-Strings und nicht `+`:

Code: Alles auswählen

print(f"{muenze}er: {anzahl_muenzen}")
Und um das `=` bei Schlüsselwortargumenten in Funktionsaufrufen werden keine Leerzeichen gesetzt:

Code: Alles auswählen

MUENZTYPEN.sort(reverse=True)
imonbln
User
Beiträge: 191
Registriert: Freitag 3. Dezember 2021, 17:07

Als Erstes solltest du bei Finanzmathematischen Programmen immer in Cent oder zehntel Cent rechen, dann ersparst du dir den ärger mit den Rundungsproblemen bei Float und du kannst nur Integer Arbeiten.

Wenn du in Cents statt Euro rechnest, dann kann divmod dein Freund sein, da divmod dir, den Quotienten und den Rest einer Integer-Division zurückgibt, kannst du relativ einfach die minimale Anzahl an Münzen ermitteln.
Indem du die Nennwerte der Münzen (in Cent), von Groß nach Klein durch den gesuchten Betrag teilst. Der Quotient den aktuellen Nennwert der Münze ist die Anzahl der Münzen, des aktuellen Werts welche du zurückgeben willst, der Rest ist der neue gesuchte Betrag. Wen der gesuchte Betrag Null ist, bist du fertig und hast die beste Kombination für die minimal Anzahl von Münzen gefunden.
Ich weiß, meine Beschreibung klingt etwas kompliziert, aber der Code ist relativ einfach ungefähr so:

Code: Alles auswählen

def change_coins(amount_in_cents: int):
    # Note the order: the coins must be sorted from large to small,
    # otherwise the algorithm will not work.
    for coin in [200, 100, 50, 20, 10, 5, 2, 1]:
        unit = "Cents"
        no_of_coins, amount_in_cents = divmod(amount_in_cents, coin)
        if no_of_coins > 0:
            if coin >= 100:
                # Convert to Euros
                coin = coin // 100
                unit = "Euros"
            print(f"{no_of_coins:02d} coins of {coin:2d} {unit}")
        if amount_in_cents == 0:
            # desired amount has been reached
            break


def main():
    while True:
        try:
            amount_in_euros = float(
                input("please state the desired amount in euros: "))
            amount_in_cents = int(amount_in_euros * 100)
            break
        except ValueError:
            pass
    change_coins(amount_in_cents)


if __name__ == "__main__":
    main()

Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

@imonbln: beim Rechnen mit floats muß man sehr gut überlegen, welche Rechenoperation zum richtigen Ergebnis führt, hier statt `int` `round`.
Feedback1000
User
Beiträge: 88
Registriert: Dienstag 20. September 2022, 21:21

Sirius3 hat geschrieben: Donnerstag 3. November 2022, 22:50 @imonbln: beim Rechnen mit floats muß man sehr gut überlegen, welche Rechenoperation zum richtigen Ergebnis führt, hier statt `int` `round`.
Wie würde dann deines Erachtens der Code besser sein?
imonbln
User
Beiträge: 191
Registriert: Freitag 3. Dezember 2021, 17:07

Feedback1000 hat geschrieben: Freitag 4. November 2022, 00:51 Wie würde dann deines Erachtens der Code besser sein?
Das hat Sirus doch geschrieben, statt int schlägt er vor hier Mathematisch Korrekt zu arbeiten mit round. Damit Beträge wie 3.555 € zu 3,56 € gerundet werden und nicht abgeschnitten.

falsch da mit int gecastet wird

Code: Alles auswählen

amount_in_cents = int(amount_in_euros * 100)
Besser:

Code: Alles auswählen

amount_in_cents = round(amount_in_euros * 100)
Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

Darum geht es:

Code: Alles auswählen

In [1]: int(float("0.29") * 100)
Out[1]: 28
Feedback1000
User
Beiträge: 88
Registriert: Dienstag 20. September 2022, 21:21

Nach ein paar Tests bin ich dann doch der Meinung, dass es so funktioniert, allerdings bin ich mit

Code: Alles auswählen

betrag = int(round(float(input('Den Betrag eingeben: ')) * 100))
nicht wirklich zufrieden, denn es "sieht nicht schön aus" und es ärgert mich, dass man die finale Ausgabe auch noch einmal bearbeiten muss (Div. mit 100)

Code: Alles auswählen

MUENZTYPEN = [
    1,
    2,
    5,
    10,
    20,
    50,
    100,
    200
]

MUENZTYPEN.sort(reverse = True)
#print(MUENZTYPEN)

betrag = int(round(float(input('Den Betrag eingeben: ')) * 100))
#print(betrag)

for muenze in MUENZTYPEN:
    anzahl_muenzen = 0
    while betrag >= muenze:
        anzahl_muenzen += 1
        betrag -= muenze
    print(f'Anzahl der {muenze / 100:.2f} € Münze: {anzahl_muenzen}')
Was könnte man schöner machen? Könnt ihr mir Hinweise geben?
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Feedback1000: Das sollte Dich nicht ärgern, weil das relativ normal ist, dass man an den Punkten wo Daten das Programm betreten oder verlassen, Code hat, der bei der Eingabe die Daten in eine Form bringt in der man am praktischsten intern damit arbeiten kann, bei der Ausgabe dann die Daten so aufbereitet, dass sie für den Benutzer sinnvoll zu lesen sind.

Ich würde da eher noch mehr Code schreiben wollen, nämlich das bei der Eingabe auch das Komma möglich ist und bei der Ausgabe der Punkt durch das Komma ersetzt wird, denn der Punkt hat in Zahlen im deutschsprachigen Raum ja eigentlich eine andere Bedeutung. In der Regel als Trennzeichen zwischen Tausendern. Und ich würde wenn es noch “schöner“ sein soll, nicht für alle Werte € als Grundlage nehmen, denn „0,10 € Münze“ ist ja eher nicht der normale Sprachgebrauch. Da würde man „10 ¢ Münze“ zu sagen.

Wenn ich am Programm etwas verbessern oder erweitern würde, dann eher das Hauptprogramm in eine Funktion stecken und die Benutzereingabe gegen Falscheingaben absichern. Also keine Zahlen, negative Zahlen, Anteile der Eingabe die unter einem Cent liegen, so was.

Die Schleife finde ich unschön, weil man das in *einem* Rechenschritt machen kann. `divmod()` wurde ja schon in vorherigen Beiträgen gezeigt. Falls jemand eine Million in Münzen haben möchte, wird die schleife 500.000 mal durchlaufen, statt eine Division und eine Modulo Operation zu machen, die in `divmod()` wahrscheinlich noch optimiert sind.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
DeaD_EyE
User
Beiträge: 1243
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Wieso hat denn niemand Decimal erwähnt?
Verwendet es überhaupt jemand ernsthaft?

Ich habe mich einfach mal nach dem Code im Eingangspost gerichtet und erweitert.

Code: Alles auswählen

from decimal import Decimal, InvalidOperation

# Vorgabe der Münzen als str
MUENZEN = "0.01 0.02 0.05 0.10 0.20 0.50 1.00 2.00".split()
# in Decimal umwandeln
MUENZTYPEN = list(map(Decimal, MUENZEN))
MUENZTYPEN.sort(reverse=True)

# Typehint (nicht erforderlich, kann aber helfen wenn man eine IDE+mypy verwendet)
ERGEBNIS = dict[Decimal, int]


def auszahlung() -> ERGEBNIS:
    # solange abfragen, bis der Nutzer einen gültigen Wert eingegeben hat
    while True:
        eingabe = input("Betrag: ")
        try:
            auszahlung = Decimal(eingabe)
        except InvalidOperation:
            print(f"Die Eingabe '{eingabe}' ist ungültig")
        else:
            break

    # mapping: Muenze -> Anzahl
    ergebnis = {}
    for muenze in MUENZTYPEN:
        # assignment expression
        # wenn anzhal == 0, wird der Block nicht ausgeführt
        if anzahl := auszahlung // muenze:
            # Teilergebnis zuweisen
            ergebnis[muenze] = int(anzahl)
            # Teilergebnis von auszahlung subtrahieren
            auszahlung -= muenze * anzahl

    # darf normal nicht vorkommen, aber wenn der Nutzer z.B.
    # 13.333 eingibt, bleiben 0.003 Restbetrag übrig.
    if auszahlung != Decimal(0):
        print(f"'{auszahlung}' € können nicht ausgezahlt werden")

    return ergebnis


def summieren(muenzen: ERGEBNIS) -> Decimal:
    """
    Muenzen * Anzahl aufsummieren
    """
    return Decimal(sum(m * a for m, a in muenzen.items()))


def summieren_cent(muenzen: ERGEBNIS) -> int:
    return int(summieren(muenzen) * 100)


if __name__ == "__main__":
    ausgabe = auszahlung()
    print("Folgende Muenzen werden ausgegeben:")
    for muenze, anzahl in ausgabe.items():
        print(f"{muenze} x {anzahl} == {muenze * anzahl}")
    summe = summieren(ausgabe)
    print(f"Summe: {summe}")
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
Dennis89
User
Beiträge: 1560
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich hätte mal eine Zwischenfrage zu dem gezeigten Code. @DeaD_EyE ich meine das ich in deinen Codebeispielen häufiger so etwas sehe

Code: Alles auswählen

def auszahlung() -> ERGEBNIS:

Code: Alles auswählen

def summieren(muenzen: ERGEBNIS) -> Decimal
Also den Pfeil. Der Code würde auch ohne funktionieren und ich wollte wissen welche Vorteile das mit sich bringt bzw. in welchen Fällen macht sich das bemerkbar?

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Das macht sich bemerkbar wenn man Werkzeuge wie `mypy` verwendet, die diese Typannotationen prüfen. Was man auf jeden Fall tun sollte wenn man Typannotationen in den Quelltext schreibt, denn fehlerhafte Typannotationen sind mindestens so schlimm wie inhaltlich fehlerhafte Kommentare. Der Mehrwert von Typannotationen ist, das der Leser weiss was da zurückgegeben wird, beziehungsweise an anderen Stellen was als Argument(e) erwartet wird.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Dennis89
User
Beiträge: 1560
Registriert: Freitag 11. Dezember 2020, 15:13

Vielen Dank für die schnelle Antwort.
Damit verabschiede ich mich wieder aus dem Thema, sorry für die kleine Störung.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
DeaD_EyE
User
Beiträge: 1243
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

__blackjack__ hat geschrieben: Donnerstag 17. November 2022, 23:18 @Dennis89: Das macht sich bemerkbar wenn man Werkzeuge wie `mypy` verwendet, die diese Typannotationen prüfen. Was man auf jeden Fall tun sollte wenn man Typannotationen in den Quelltext schreibt, denn fehlerhafte Typannotationen sind mindestens so schlimm wie inhaltlich fehlerhafte Kommentare. Der Mehrwert von Typannotationen ist, das der Leser weiss was da zurückgegeben wird, beziehungsweise an anderen Stellen was als Argument(e) erwartet wird.
Falsche Typannotationen sind noch viel schlimmer als gar keine Typannotationen.

Das ist nur zu empfehlen, wenn man eine IDE verwendet, mypy im Dauereinsatz hat und die Vorteile der Typenannotationen auch nutzt.
Schreibt man z.B. eine Funktion mit Typehints, schlägt einem die IDE die Methoden der jeweiligen Klasse vor.

Ohne die Annotation weiß die IDE nicht, um welchen Typen es sich handeln soll. Zur Laufzeit wird nicht überprüft.
Das kann jeder selbst ausprobieren:

Code: Alles auswählen

from pathlib import Path


def test(path: Path):
    path.[TABULATOR]
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Und Typannotationen sind sehr oft falsch, selbst wenn sie formal richtig sind, weil die Leute in der Regel zu unwissend oder zu faul sind, sich da die nötigen Gedanken sowohl zu Typen als auch Typnamen zu machen, dass es am Ende schlechter als Duck-Typing ist. 😱

`dict` ist als Typ beispielsweise falsch wenn `Mapping` ausreicht. Und das ist nicht nur ein anderer Name für `dict`, da ist sogar noch mindestens ein Typ ”dazwischen”: `MutableMapping`. Wenn eine Funktion die Abbildung nicht verändert, sollte man keinen Typ angeben, der das vom Aufrufer fordert. Denn was macht man wenn man eine nichtveränderbare Abbildung hat? Die einzig und alleine weil jemand nicht ausreichend über den Typ bei der Annotation nachgedacht hat, noch mal sinnlos in eine veränderbare Abbildung umkopieren? Also den Schwachsinn, den man leider zu oft bei Sprachen wie Java machen muss wenn da Leute nicht für 50 ¢ über ihre APIs nachgedacht haben? Nein danke.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

DeaD_EyE hat geschrieben: Sonntag 20. November 2022, 13:42 Das ist nur zu empfehlen, wenn man eine IDE verwendet, mypy im Dauereinsatz hat und die Vorteile der Typenannotationen auch nutzt.
Das kann man gar nicht ausdrücklich genug betonen.

Und dann sollte man dies konsequent auch bei allen Funktions-Aufrufen und -Definitionen anwenden – und nicht nur ein bisschen hier und ein wenig dort.

Und nicht wundern, wenn dabei "readabilty counts" leidet. Die Vorteile die man aus Typeannotations zu schöpfen glaubt, sollten dies auf jeden Fall überkompensieren.
Antworten