Max(Liste) funktioniert ... "unerwartet"

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
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Ich stoße gerade mal wieder an meine, bzw. Pythons Grenzen. Ich habe ein Primzahlprogramm geschrieben, das aus einer Datei mit Primzahlen die Abstände berechnet. Das funktioniert soweit auch gut. Der Plot ist okay und scheint mathematisch zu stimmen. Wenn ich allerdings aus der Liste mit den Abständen den Maximalwert (laut Plot und Liste "20") herausfiltern möchte, sagt die Max()-Funktion "8". Warum? Was mache ich falsch?

Hier der Code (ich weiß, es geht sicher schöner und mehr pythonisch, aber das ist halt meiner):

Code: Alles auswählen

import matplotlib.pyplot as plt

file = open("primes_in1000.txt", "r")
delta_list = []
number_of_primes = 1
previous_number = 2
all_lines_of_file = file.readlines()
for line in all_lines_of_file:
    print("Vorige Primzahl: ", previous_number)
    current_number = int(line)
    print("Aktuelle Primzahl: ", current_number)
    delta_primes = current_number - previous_number
    print("Abstand: ", delta_primes)
    previous_number = current_number
    delta_list.append(str(delta_primes))
    number_of_primes +=1

print(delta_list)
print(type(delta_list))
print(number_of_primes, "Zahlen berechnet.")
delta_max = max(delta_list)
print("Größter Abstand zwischen zwei Primzahlen: ", delta_max)
delta_min = min(delta_list)
print("Kleinster Abstand zwischen zwei Primzahlen: ", delta_min)

plt.plot(delta_list)
plt.show()
file.close()
Und hier ist die Ausgabe:

Vorige Primzahl: 991
Aktuelle Primzahl: 997
Abstand: 6
['1', '2', '2', '4', '2', '4', '2', '4', '6', '2', '6', '4', '2', '4', '6', '6', '2', '6', '4', '2', '6', '4', '6', '8', '4', '2', '4', '2', '4', '14', '4', '6', '2', '10', '2', '6', '6', '4', '6', '6', '2', '10', '2', '4', '2', '12', '12', '4', '2', '4', '6', '2', '10', '6', '6', '6', '2', '6', '4', '2', '10', '14', '4', '2', '4', '14', '6', '10', '2', '4', '6', '8', '6', '6', '4', '6', '8', '4', '8', '10', '2', '10', '2', '6', '4', '6', '8', '4', '2', '4', '12', '8', '4', '8', '4', '6', '12', '2', '18', '6', '10', '6', '6', '2', '6', '10', '6', '6', '2', '6', '6', '4', '2', '12', '10', '2', '4', '6', '6', '2', '12', '4', '6', '8', '10', '8', '10', '8', '6', '6', '4', '8', '6', '4', '8', '4', '14', '10', '12', '2', '10', '2', '4', '2', '10', '14', '4', '2', '4', '14', '4', '2', '4', '20', '4', '8', '10', '8', '4', '6', '6', '14', '4', '6', '6', '8', '6']
<class 'list'>
168 Zahlen berechnet.
Größter Abstand zwischen zwei Primzahlen: 8
Kleinster Abstand zwischen zwei Primzahlen: 1

Process finished with exit code 0
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@viechdokter: Du hast da keine Zahlen sondern Zeichenketten. Zeichenketten werden Zeichen für Zeichen verglichen. Erst das erste Zeichen von beiden Zeichenketten, wenn die gleich sind, dann das zweite von beiden Zeichenketten und so weiter. Bei '8' > '10' ist das aber schon beim ersten Zeichen klar, das '8' grösser ist als '1' und damit auch '8' grösser ist als '10'.

Code: Alles auswählen

In [108]: 8 > 10                                                                
Out[108]: False

In [109]: "8" > "10"                                                            
Out[109]: True
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@viechdokter: Trotzdem noch Anmerkungen zum Code, damit auch Dein Code noch besser werden kann in Zukunft:

Die Pimzahlendatei wird erst sehr spät wieder geschlossen. Es ist besser das näher an dem Zeitpunkt im Programmablauf zu tun zu dem die Datei nicht mehr benötigt wird. Und am besten kombiniert man das öffnen einer Datei mit der ``with``-Anweisung, dann wird die Datei auch geschlossen wenn etwas unvorhergesehenes wie eine Ausnahme auftritt.

Es gibt keinen Grund die Zeilen der Datei erst komplett in den Speicher zu laden. Dateiobjekte sind iterierbar und liefern dabei die Zeilen der Datei.

`number_of_primes` ist redundant und potentiell vom Wert her falsch wenn man das einfach mit 1 initialisiert. Redundant, weil sich der Wert aus der Anzahl der Elemente in `delta_list` ergibt. Falsch, weil der Startwert 1 für eine leere Datei nicht stimmen würde.

Grunddatentypen gehören eher nicht in Namen. Den Typen ändert man nicht selten im Laufe der Programmentwicklung in etwas spezialisierteres oder auch etwas generischeres wie allgemein was iterierbares, und dann hat man im Programm entweder falsche, irreführende Namen stehen, oder man muss die Daten durch das Programm verfolgen und überall die betroffenen Namen anpassen.

Der Initialwert von `previous_number` geht davon aus, dass die Primzahlen in der Datei auch wirklich bei 2 anfangen. Robuster/flexibler wäre es da wirklich am Anfang der Daten aus der Datei zu beginen, und den Wert auch gar nicht mit zu berechnen, sondern wirklich nur Abstände aus den tatsächlich in der vorliegenden Datei zu berechnen.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import matplotlib.pyplot as plt


def main():
    with open("primes_in1000.txt", "r", encoding="ascii") as lines:
        numbers = map(int, lines)
        deltas = []
        previous_number = next(numbers)
        for current_number in numbers:
            print("Vorige Primzahl: ", previous_number)
            print("Aktuelle Primzahl: ", current_number)
            prime_delta = current_number - previous_number
            print("Abstand: ", prime_delta)
            previous_number = current_number
            deltas.append(prime_delta)

    print(deltas)
    print(type(deltas))
    print(len(deltas), "Zahlen berechnet.")
    delta_max = max(deltas)
    print("Größter Abstand zwischen zwei Primzahlen: ", delta_max)
    delta_min = min(deltas)
    print("Kleinster Abstand zwischen zwei Primzahlen: ", delta_min)

    plt.plot(deltas)
    plt.show()


if __name__ == "__main__":
    main()
Wenn man die ganzen detaillierten Ausgaben in der Schleife nicht braucht, kann man `more_itertools.pairwise()` verwenden, was es auch als ”Rezept” in der Dokumentation vom `itertools`-Modul in der Standardbibliothek gibt:

Code: Alles auswählen

#!/usr/bin/env python3
import matplotlib.pyplot as plt
from more_itertools import pairwise


def main():
    with open("primes_in1000.txt", "r", encoding="ascii") as lines:
        deltas = [
            current_number - previous_number
            for previous_number, current_number in pairwise(map(int, lines))
        ]

    print(deltas)
    print(type(deltas))
    print(len(deltas), "Zahlen berechnet.")
    delta_max = max(deltas)
    print("Größter Abstand zwischen zwei Primzahlen: ", delta_max)
    delta_min = min(deltas)
    print("Kleinster Abstand zwischen zwei Primzahlen: ", delta_min)

    plt.plot(deltas)
    plt.show()


if __name__ == "__main__":
    main()
Was hier IMHO inhaltlich falsch ist, ist die Diagrammart. So ein Linendiagramm suggeriert, dass es zwischen den ”Messwerten” interpolierbare, sinnvolle Zwischenwerte gäbe.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Hallo blackjack,

schön, mal wieder von dir zu hören. Vielen, vielen Dank für die ausführliche Antwort und die Verbesserungsvorschläge. Ich hatte schon so ein Bauchgefühl, daß es an strings liegen könnte, aber int(delta) hat nur zu weiteren Fehlermeldungen geführt. Python ist da sehr hilfreich. (Manche würden es auch als "nachtragend" bezeichnen...)
Einige Anmerkungen, warum ich das Programm so geschrieben habe:

1. bei meinem ersten Versuch, die Datei Zeile um Zeile auszulesen mit datei.readline() bekam ich nur jede zweite Primzahl aus der Datei zu sehen. Ich weiß nicht, warum, aber ich nehme an, da ist mein Programm über irgendwelche \n gestolpert. In der Datei steht pro Zeile eine Zahl und ein newline. Das war der Grund, weshalb ich dann lieber die ganze Datei in einem Rutsch ausgelesen habe. Das hat wenigstens auf Anhieb funktioniert.

2. number_of_primes hatte ich vor der Schleife mit 1 initialisiert, weil die erste Primzahl, die 2, ja auch schon vor der Schleife steht. Wenn man das Programm allgemeiner machen wollte, damit es von jeder Startzahl ausgeht, wäre das natürlich Quatsch. Hast du vollkommen recht. Die vielen Zwischenausgaben habe ich auch nur gemacht, weil ich die Programmausgaben überprüfen wollte. Dabei hatte ich auch gesehen, daß mein erstes Programm nur jede zweite Zeile gelesen hatte. Ich mache immer esrtmal viele Ausgaben und reduziere sie dann, wenn ich meinem Programm trauen kann.

3. Die itertools werde ich mir demnächst mal vornehmen. Die kannte ich noch nicht. Danke für den Hinweis.

4. Der Plot war nur für den schnellen Überblick gedacht, weshalb ich mir damit keine Mühe gegeben habe. Er zeigte mir sofort, daß es da einen Abstand "20" geben musste. Scatter wäre wohl besser.

5. Was meinst du mit "Grunddatentypen im Namen"?

6. Was hat es mit

if __name__ == "__main__":
main()

auf sich?
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@viechdokter: Ad 1. Jede zweite Zeile klingt eher nach so etwas wie ``for`` über die Datei *und* `readline()`:

Code: Alles auswählen

for item in file:
    line = file.readline()
    print(line)
So wird beispielsweise nur jede zweite Zeile ausgegeben, weil die Zeilen abwechselnd an `item` und an `line` gebunden werden, und mit `item` nichts gemacht wird. Oder ein anderer Fehler den man manchmal sieht:

Code: Alles auswählen

while file.readline():
    line = file.readline()
    print(line)
Da wird natürlich auch nur jede zweite Zeile an `line` gebunden.

Ad 5.: `str`, `int`, `list`, `dict` & Co. Also konkret `delta_list`. Wenn ich das einlesen dieser Daten in eine Funktion auslagern würde, dann würde ich da beispielsweise sehr wahrscheinlich einen Generatorausdruck draus machen, damit der Aufrufer entscheiden kann, ob er tatsächlich alle Datein auf einmal braucht, oder ob ihm ein iterierbares Objekt ausreicht.

Ad 6.: `__name__` enthält den Modulnamen, ausser wenn das Modul als Programm ausgeführt wird, dann ist das der Wert "__main__". So kann man das Modul importieren, ohne dass das Programm los läuft. Zum Beispiel um interaktiv in einer Python-Shell oder automatisiert von einem anderen Modul aus die Funktionen/Klassen in einem Modul zu testen. Auch andere Werkzeuge importieren Module und verlassen sich darauf, das dadurch kein Hauptprogramm mit irgendwelchen Seiteneffekten läuft. Beispielsweise um Dokumentation zu extrahieren oder statische Analyse zu machen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Genau das war's! For und readline()! Du bist echt gut! Danke für die Erklärung.
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Habe jetzt die strings der delta_list in integer umgewandelt:

Code: Alles auswählen

int_delta_list = [eval(x) for x in delta_list]
delta_max = max(int_delta_list)
Jetzt rechnet er richtig. Danke nochmal für den Hinweis. Die Verbesserungsvorschläge werde ich später einarbeiten.
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@viechdokter: Argh, nicht mit `eval()`. 😱 Das wertet beliebige Python-Ausdrücke aus, also beispielsweise auch ``__import__("os").system("ls ~/*")``. Und statt alles aufzulisten, könnte man da auch alles löschen. Für Umwandlung von Zeichenkettendarstellung einer ganzen Zahl in eine ganze Zahl gibt es das wesentlich ungefährlichere `int()`.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Okay, nochmal danke. Habe es geändert.

Aber jetzt habe ich wieder ein neues Problem. Ich habe mir mal den Plot für Primzahlen bis 634237 angesehen, der (immer noch) quick and dirty mit plt.plot() erstellt wurde. Da fällt bei der automatischen Beschriftung der y-Achse auf, daß die y-Zahlen total durcheinander geraten.

Bild

https://ibb.co/G9YDjft

0,1,2,4,6,8,14,10,12,18,20,22,34,24,16...

Warum ist das so? Kann man so einem Plot dann überhaupt trauen?
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Sirius3
User
Beiträge: 17709
Registriert: Sonntag 21. Oktober 2012, 17:20

Und wie sieht der Code dazu aus?
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Hallo Sirius, schön wieder mal von dir zu hören. Der Code ist nicht optimiert, aber er funktioniert erstmal. Ich habe schon Verbesserungsvorschläge erhalten. Es geht im Moment auch eher um das seltsame Verhalten von plt.plot(). Die Werte auf der y-Achse sind einfach total durcheinander... Hier der Code:

Code: Alles auswählen

import matplotlib.pyplot as plt

file = open("primes.txt", "r")
delta_list = []
number_of_primes = 1
previous_number = 2
all_lines_of_file = file.readlines()
file.close()
for line in all_lines_of_file:
    print("Vorige Primzahl: ", previous_number)
    current_number = int(line)
    print("Aktuelle Primzahl: ", current_number)
    delta_primes = current_number - previous_number
    print("Abstand: ", delta_primes)
    previous_number = current_number
    delta_list.append(str(delta_primes))
    number_of_primes +=1

print(delta_list)
print(type(delta_list))
print(number_of_primes, "Zahlen berechnet.")
int_delta_list = [int(x) for x in delta_list]
delta_max = max(int_delta_list)
print("Größter Abstand zwischen zwei Primzahlen: ", delta_max)
delta_min = min(int_delta_list)
print("Kleinster Abstand zwischen zwei Primzahlen: ", delta_min)

plt.plot(delta_list)
plt.show()
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Sirius3
User
Beiträge: 17709
Registriert: Sonntag 21. Oktober 2012, 17:20

Da ist ja immer noch der selbe Fehler drin, den __blackjack__ schon in seinem ersten Posting korrigiert hatte. Man arbeitet nicht mit Strings, wenn man eigentlich Zahlen hat.
Du versuchst Strings plotten und da sortiert Matplotlib die Werte, in der Reihenfolge des ersten Erscheinens.
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Oh Mann! Ich wusste gar nicht, dass Plot auch Strings verarbeitet! Danke für die Erklärung. Ich habe in den letzten Tagen eine Menge gelernt. Cool, dass ihr euch die Zeit genommen habt, einem Rookie das zu erklären!
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@viechdokter: Man könnte ja beispielsweise Y-Werte in einem Jahresverlauf haben und die X-Achse mit "Frühling", "Sommer", "Herbst", und "Winter" beschriften wollen. Das wären dann Zeichenketten.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
viechdokter
User
Beiträge: 41
Registriert: Samstag 11. Dezember 2021, 20:52

Stimmt. War mir bisher nicht bewußt.
Python..., wie sage ich das jetzt am besten...?
Es liegt nicht an dir. Es liegt an mir...!
Antworten