Anfänger: Problem bei Code

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
Akolon
User
Beiträge: 4
Registriert: Freitag 8. April 2016, 15:32

Hallo erstmal. Ich habe vor ca. 1 Monat angefangen, Python zu lernen und habe mir jetzt die grundlegensten Dinge wie Schleifen u.Ä. beigebracht, bin aber trotzdem noch ein ziemlicher Anfänger. Jetzt mal zu meinem Problem: Ich habe ein kleines Programm geschrieben (unten), das zuerst 100.000 mal zufällig eine Nummer zwischen 1 & 9 auswählt, dann die absolute Häufigkeit jeder Nummer zählt und die relative Häufigkeit berechnet. Danach wollte ich noch schauen, ob alles passt, also habe ich alle rel. Häufigkeiten addiert. Theoretisch müsste dabei 1 herauskommen, allerdings ist es immer 1.00001 oder 1.00000999... . Keine Ahnung, wieso...

Schon mal Danke im Voraus :)

Code: Alles auswählen

from random import randint
zaehler = 0
zliste = []
zanzahl = []
x = 0
while zaehler <= 100000:
    zahl = randint(1,9)
    zliste.append(zahl)
    zaehler = zaehler + 1
zaehler = 0
while zaehler <= 9:
    zaehler = zaehler + 1
    zanzahl.append(zliste.count(zaehler))
zaehler = 0
while zaehler <= 8:
    print(zanzahl[zaehler]/100000)
    x = x + (zanzahl[zaehler]/100000)
    zaehler = zaehler + 1
print ("Gesamt: ",x)
BlackJack

@Akolon: Gleitkommaarithmetik ist nicht exakt weil der `float`-Datentyp nur eine begrenzte Genauigkeit hat. Siehe auch What Every Programmer Should Know About Floating-Point Arithmetic or Why don’t my numbers add up?.
Akolon
User
Beiträge: 4
Registriert: Freitag 8. April 2016, 15:32

@BlackJack Achso, danke :)
BlackJack

@Akolon: Wobei hier tatsächlich noch ein anderer Fehler vorliegt. Wieviele Zufallszahlen ziehst Du in dem Code‽
Akolon
User
Beiträge: 4
Registriert: Freitag 8. April 2016, 15:32

@BlackJack 9, da ja bei randint die Zahlen zwischen 1 und 9 ausgewählt werden.. Ansonsten weis ich nicht, was du meinst :?
BlackJack

@Akolon: Ich meine nicht wie viele unterschiedliche Werte sondern wie viele Werte insgesamt. Also wie lang ist `zliste` nach dem ziehen der Zufallszahlen. Tipp: 100000 ist die falsche Antwort. Aber der Rest des Codes geht von diesem Wert aus.
BlackJack

@Akolon: Noch ein paar Anmerkungen zum Quelltext:

Namen sollten dem Leser klar vermitteln was der Wert dahinter im Programm bedeutet. Deswegen sind kryptische Abkürzungen oder einbuchstabige Namen schlecht. `x` wäre okay wenn es sich um eine `x`-Koordinate handelt, weil man diesen Namen dafür gewohnt ist, aber für eine Gesamtsumme ist das kein guter Name. Ähnlich gilt das für `zliste` und `zanzahl`, bei denen nicht direkt klar ist warum das jetzt gerade `z` ist (weil es nicht für eine `z`-Koordinate steht). Ebenfalls problematisch ist das kodieren von konkreten Grunddatentypen in Namen, denn man verändert öfter mal während der Programmentwicklung den Datentyp, zum Beispiel weil an anderer Sequenzdatentyp als `list` oder gar ein selbst geschriebener Datentyp mehr Sinn macht. Und dann muss man entweder überall die Namen anpassen, oder man hat falsche und irreführende Namen im Programm. Üblicherweise wählt man für einfache Containerdatentypen die Mehrzahl von der Bezeichnung die für ein einzelnes Element passend wäre. Also statt `zliste` beispielsweise `zahlen`.

Anstelle einer Liste bei `zanzahl` würde ein Wörterbuch etwas selbsterklärender sein, weil die Abbildung nicht bei 0 anfängt und man diese Verschiebung um 1 sonst immer im Kopf haben muss wenn man mit dieser Datenstruktur arbeitet. An der Stelle ist auch der willkürliche Unterschied zwischen den beiden letzten ``while``-Schleifen was den Test und die Position der Erhöhung von `zaehler` angeht verwirrend.

Diese ``while``-Schleifen sollten sowieso allesamt ``for``-Schleifen sein. ``while``-Schleifen verwendet man, wenn man vor eintritt der Schleife nicht ermitteln kann, nach wie vielen Schleifendurchläufen abgebrochen werden muss. Bei allen Schleifen in diesem Programm weiss man aber genau wie oft die durchlaufen werden. Damit entfällt jeweils die Initialisierung von `zaehler` vor der Schleife, sowie das manuelle hochzählen des Wertes innerhalb der Schleife. Und der Fehler den Du in der ersten Schleife gemacht hast, ist unwahrscheinlicher wenn man diese Sachen nicht selber von Hand erledigt. :-)

Namen sollte man nach möglichkeit erst dort definieren, wo sie auch tatsächlich benutzt werden. Also nicht irgendwo am Anfang alle möglichen Namen an Werte binden, die erst viel später in der Funktion tatsächlich verwendet werden. Das macht den Quelltext unübersichtlicher und schwerer wartbar, weil es zum Beispiel unnötig aufwändiger wird eine zu lange Funktion in kleinere Teilfunktionen zu zerlegen.

Der Fehler der durch die erste Schleife passiert ist, könnte dadurch ausgeglichen werden, dass man weniger ”magische” Zahlen verwendet. Statt also am Ende für die Wahrscheinlichkeit eine feste Gesamtzahl der Ziehungen zu verwenden, könnte man die tatsächliche Anzahl der vorliegenden Werte ausrechnen und verwenden.

Auf jeden Fall sollte man ”magische” Zahlen nicht mehrfach im Quelltext stehen haben. Das macht das anpassen des Programms aufwändiger und fehleranfällger, wenn man sich beispielsweise andere Anzahlen von Ziehungen oder andere Wertebereiche für die Zufallszahlen ansehen möchte. In so einem Fall sollte man die jeweilige Zahl nur an *einer* Stelle im Quelltext ändern müssen.

Code und Daten sollten sich nicht wiederholen. Die Berechnung der Wahrscheinlichkeit für eine Zahl führst Du zwei mal durch.

Anweisungen der Form ``x = x + y`` lassen sich kürzer als ``x += y`` schreiben.

Ich lande dann ungefähr bei so etwas:

Code: Alles auswählen

from random import randrange


def main():
    gesamtanzahl = 100000
    untergrenze = 1  # Inklusive.
    obergrenze = 10  # Exklusive.
    assert untergrenze < obergrenze

    zahlen = list()
    for _ in range(gesamtanzahl):
        zahlen.append(randrange(untergrenze, obergrenze))

    zahl_zu_anzahl = dict()
    for zahl in range(untergrenze, obergrenze):
        zahl_zu_anzahl[zahl] = zahlen.count(zahl)

    assert sum(zahl_zu_anzahl.values()) == gesamtanzahl
    gesamtwahrscheinlichkeit = 0
    for zahl, anzahl in sorted(zahl_zu_anzahl.items()):
        wahrscheinlichkeit = anzahl / gesamtanzahl
        print(zahl, wahrscheinlichkeit)
        gesamtwahrscheinlichkeit += wahrscheinlichkeit

    print('Gesamt: ', gesamtwahrscheinlichkeit)


if __name__ == '__main__':
    main()
Aus der ersten Schleife könnte man jetzt noch eine „list comprehension“ machen.

Der Algorithmus zum zählen mit `count()` ist ineffizient, denn statt die `zahlen` *einmal* komplett durchzugehen, werden sie für jede der neun Zahlen komplett durchgegangen. Die Standardbibliothek enthält mit `collections.Counter` ausserdem einen Datentyp, der genau für dieses Szenario passt, so dass man das zählen gar nicht selber implementieren muss.
Akolon
User
Beiträge: 4
Registriert: Freitag 8. April 2016, 15:32

@BlackJack
Zum 1. Beitrag:
Ja, habe den Fehler jetzt auch bemerkt, es waren 100001..

Zum 2. Beitrag:
Das mit den Namen werde ich aufjedenfall berücksichtigen. Bis jetzt habe ich nur kleine Programme geschrieben, deshalb haben mich auch solche Variabelnamen wie 'x' nicht wirklich gestört, aber sich eine gut lesbare Schreibweise anzueignen ist wohl von Vorteil.

Dictionaries werden in meinem Buch weiter hinten genauer behandelt, bisher weis ich nur, dass es sie gibt. Eine Liste ist die einzige Mögkichkeit, mit der ich das Programm momentan umsetzen kann.

Stimmt wohl, for-Schleifen wären besser..

Hier ist es wieder der selbe Grund wie bei der Namensgebung: Bei kleinen Programmen hat mich sowas nie gestört, aber ich werd versuchen, es besser zu machen.

Die restlichen 6 Verbesserungsvorschläge versuche ich auch, umzusetzen.

Zum Code:

Danke für den Bsp-Code, aber bis jetzt kannte ich einige Ausdrücke (z.B. assert) noch nicht...


Und danke für's feedback :wink:
BlackJack

@Akolon: ``assert`` ist zum reinen Funktionieren des Codes hier ja nicht notwendig. Das ist einfach nur eine Überprüfung ob die Annahmen die auf jeden Fall stimmen sollten, auch wirklich stimmen. Und ist gleichzeitig eine Art Dokumentation die dem Leser Zusammenhänge vermittelt.
Antworten