Primzahlengenerator

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.
Benutzeravatar
ixaM
User
Beiträge: 13
Registriert: Samstag 9. Juli 2016, 15:32
Wohnort: Nähe München

Hallo erstmal.

Ich habe vor in nächster Zeit ein Textverschlüsselungsprogramm zu schreiben. Bei den Meisten dieser Art ist die Verwendung von Primzahlen ja ein essenzieller Bestandteil, weshalb ich es mir zuerst zur Aufgabe gemacht habe, einen Primzahlengenerator zu schreiben. Das Programm sieht derzeit wiefolgt aus:

Code: Alles auswählen

from decimal import *
PRIMZAHLEN = [2]
p = 3 """Die Zahl, welche gerade gecheckt wird, ob sie eine Primzahl ist."""
n = 2 """Der Teiler"""
x = int(input("Bis zu welcher Zahl möchtest du die Primzahlen berechnet bekommen?"))   """Der Grenzwert, bis zu welcher natürlichen Zahl die Primzahlen ausgegeben werden sollen."""
while True:
    if p/n == int(p/n) and p/n != 1.0:
        p = p+1
    if p/n != int(p/n):
        n = n+1
    if p/n == 1:
        PRIMZAHLEN.append(p)
        p = p+1
        n = 2
    if p > x:
        break
print(PRIMZAHLEN)
Jedoch gibt er dann verschiedenste Zahlen, die keine Primzahlen sind als solche aus. (27 zum Beispiel). Ich verstehe allerdings nicht weshalb. Wäre nett wenn ihr vielleicht mal einen Blick über´s Programm werfen könntet :)
Üpsilon
User
Beiträge: 225
Registriert: Samstag 15. September 2012, 19:23

Erstmal finde ich den Code irgendwie schwer lesbar. Statt p/n==int(p/n) könnte man auch p%n==0 schreiben, und p/n==1.0 ist gleichbedeutend mit p==n. Außerdem macht man Kommentare normalerweise mit dem #Rautezeichen. Statt p=p+1 kann man auch p+=1 schreiben und anstatt die Abbruchbedingung mit break zu regeln, könnte man sie eigentlich auch in Zeile 6 beim While benennen. Und es ist seltsam, die Liste PRIMZAHLEN im Capslock zu schreiben, das macht man eigentlich nur bei Variablen, die ihren Wert nicht ändern, während das Programm läuft.

Ohne es getestet zu haben, schätze ich mal, der Fehler liegt darin, dass du in Zeile 8 zur nächsten zu testenden Zahl übergehst, ohne wieder bei 2 als Teiler anzufangen. Soll heißen, da muss noch ein n=2 hin.

Grüße
PS: Die angebotene Summe ist beachtlich.
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Schau mal in das Topic : viewtopic.php?f=1&t=38066&start=60 dort findest du Implementionen des Primzahlensiebes.

Wenn ich richtig verstehe möchtest du testen ob eine Zahl durch eine andere, kleinere Zahl teilbar ist ?

Also das hier:

Code: Alles auswählen

def prim_primitiv(n):
    PRIMZAHLEN = [2]
    for zahl in xrange(3,n):
        for teiler in xrange(2,zahl):
            # Rest der Division zahl / teiler ist null
            if zahl % teiler == 0:
                break
        else:
            PRIMZAHLEN.append(zahl)
    return PRIMZAHLEN
Das ist übrigens die langsammst und primitvste Implementation des Teilertest verfahren..
Zuletzt geändert von pyHoax am Sonntag 18. Dezember 2016, 14:19, insgesamt 2-mal geändert.
BlackJack

@ixaM: Sternchen-Importe sollte man nicht machen. Da holt man sich alle Namen aus einem Modul in das importierende, es wird schwerer nachzuvollziehen wo eigentlich welcher Name her kommt, und es besteht die Gefahr von Namenskollisionen.

Zeichenketten sind nicht nur keine Kommentare, sondern so wie das da steht ist das syntaktisch kein Python, das heisst das was Du uns da zeigst kommt nicht einmal am Compiler vorbei. Zumal man sich hier Kommentare sparen könnte wenn man die Variablen gleich sprechend benennt und nicht `p`, `n`, und `x` die man erst erklären muss.

Das ganze ist wohl auch deshalb nicht so verständlich weil Du dort etwas in *einer* ``while``-Schleife zusammenfasst, was man eigentlich in zwei verschachtelten ``for``-Schleifen ausdrücken würde.

Wenn man sich die generierten Primzahlen merkt, kann man die innere dieser beiden Schleifen auch deutlich effizienter machen, denn man muss dann die Primzahlkandidaten gar nicht durch alle Zahlen von 2 aufwärts auf Teilbarkeit testen, sondern kann die bereits vorhandenen Primzahlen verwenden. Selbst wenn man das nicht macht, braucht man nur durch 2 und dann durch jede ungerade Zahl teilen und man muss auch nicht bis n=p testen, sondern kann deutlich früher abbrechen.

Last but not least, ist das insgesamt sehr ineffizient um alle Primzahlen bis zu einer gegebenen Obergrenze zu berechnen. Üblicher wäre es hierfür das Sieb des Erathostenes zu verwenden.

Für sinnvolle Verschlüsselung sind allerdings alle Primzahlen die man tatsächlich 100%ig in annehmbarer Rechenzeit auf diese Eigenschaft prüfen kann eigentlich zu klein.
Benutzeravatar
ixaM
User
Beiträge: 13
Registriert: Samstag 9. Juli 2016, 15:32
Wohnort: Nähe München

Danke euch! Habe jetzt ein paar Kleinigkeiten abgeändert und ein Zeitmessungsmodul eingebaut. Das Programm funktioniert jetzt!

Code: Alles auswählen

import time
PRIMZAHLEN = [2]
p = 3
n = 2
x = int(input("Bis zu welcher Zahl möchtest du die Primzahlen berechnet bekommen?"))
anfang = time.time()
while True:
    if p/n == int(p/n) and p/n != 1.0:
        p = p+2
        n = 2
    if p/n != int(p/n):
        n = n+1
    if p/n == 1:
        PRIMZAHLEN.append(p)
        p = p+2
        n = 2
    if p > x:
        break
print(PRIMZAHLEN)
ende = time.time()
print("Die Programmlaufdauer beträgt", ende - anfang, "Sekunden")
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

ixaM hat geschrieben:Das Programm funktioniert jetzt!
Nope, Ich bekomme nur [2,3] als Primzahlen geliefert.
BlackJack

@pyHoax: Das würde in Python 2 der Fall sein wenn der Compiler da nicht schon vorher wegen den Umlauten ohne Kodierungskommentar aussteigen würde. In Python 3 bekommt man mehr als diese beiden Zahlen (wenn x entsprechend gross gewählt ist).

@ixaM: Das Programm gibt immer mindestens 2 und 3 aus, auch wenn man die Frage mit 0, 1, oder 2 beantwortet. Das würde ich als Fehler ansehen.
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

In Python 3 bekommt man mehr als diese beiden Zahlen
Ahaja der Int/Float Divisor....

ICh hab es mal umgeschrieben so das es auf python 2 und 3 laufen wird.. denk ich.
[codebox=python file=teilerprim.py ]
while True:
if (p % n == 0) and p != n:
p = p + 2
n = 2
if (p % n != 0):
n = n+1
if p == n:
PRIMZAHLEN.append(p)
p = p + 2
n = 2
if p > x:
break
[/code]

Und sehr gemütlich, der kommt schon mit Primzahlen unter 1 Mio kaum unter 1-2 Zigaretten davon.
BlackJack

@pyHoax: Die Klammern bei den ``if``-Bedingungen sind überflüssig.
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

wer man schnell großen Mengen an Primzahlen braucht: das Modul "primesieve" kann das sehr gut.

Link: https://github.com/hickford/primesieve-python

Gruß, noisefloor
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

Durch etwas umstellen spart man sich den doppelten Code

Code: Alles auswählen

while True:
    if p == n or p % n == 0:
        if p == n:
            primzahlen.append(p)
        p += 2
        n = 2
        if p > x:
            break
    else:
        n += 1
Benutzeravatar
ixaM
User
Beiträge: 13
Registriert: Samstag 9. Juli 2016, 15:32
Wohnort: Nähe München

@BlackJack Da hast du Recht, das Sieb des Erastothenes ist hier effektiver, jedoch ( meines Erachtens ) schwerer zu programmieren. Und da ich noch nicht so lange programmiere, hatte ich mir vorgenommen das erstmal auf diese recht einfache Art und Weise zu machen.

Die 2 ist absichtlich immer dabei, doch wieso 3 auch ausgegeben wird weiß ich nicht. Magst du mir das verraten, sodass ich es ausbessern kann? :P

@Sirius3 Danke Dir, dass du dir die Mühe gemacht hast! Werde ich im Hinterkopf behalten, wenngleich ich vorerst mein Programm verwenden werde, da ich dieses übersichtlicher finde.

@noisefloor Danke auch Dir, jedoch ziehe ich es vor, Programme zu verwenden bei denen ich wenigstens einen Teil selbst gemacht habe und nicht alles von Anderen benutze.
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

zu Lernzwecken kann man sicherlich Sache selber programmieren. Was im gegebenen Fall ja auch nicht so schwierig ist.

Aber wenn's fertige Module gibt, dann kann man die ruhig nehmen. In der Regel sind die besser getestet, ggf. funktionsreicher und - im gegebenen Fall - auch (deutlich) schneller.

Gruß, noisefloor
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Einen hab ich noch.. einen noch.

Im folgenden habe ich das Primteiler-Verfahren mal um die üblichen Optimierungen ergänzt und (TATA !!!) die einzige Modulo (Division) im Code eliminiert.

Mit der prim_modulo Klasse kann für einen gegebene Divisor (Primzahl) entschieden werden ob eine Zahl aus einer steigenden Zahlenreihe Ohne Rest teilbar ist oder nicht.

Im Prinzip passiert in der prim_modulo Klasse das selbe was im Primzahlensieb geschieht, es wird geprüft ob eine Zahl gleich dem vielfachen der Primzahlen ist. Da die Primzahlenkandidaten in monoton-steigender Reihe getestet werden, kann das Vielfache einer Primzahl schrittweise mit entwickelt werden.

Ich hab gestern eine Abhandlung über dieses Verfahren gelesen und war überrascht wie einfach es zu implementieren war.
Enttäuscht war ich jedoch von der Python Performance - allein pypy schaft es diesen Code schneller als eine Modulo Variante auszuführen. Ist aber schon klar das es schwer wird mit reinen Interpreter-Code eine für das Problem schnellere Variante eines Buildin Symbols zuschreiben, hier wird eine C Implementierung sicherlich hilfreich sein.

Code: Alles auswählen

class prim_modulo(object):
    def __init__(self, teiler):
        self.teiler = teiler
        self.multible = teiler + teiler

    def mod(self, zahl):
        while True:
            if zahl < self.multible:
                return False
            elif zahl == self.multible:
                return True
            self.multible = self.multible + self.teiler

def prim_teiler_ohne_modulo(n):
    if n < 3:
        return [2] if n == 2 else []

    PRIMZAHLEN = []
    mroot = 0
    for zahl in range(3,n,2):
        for prim in PRIMZAHLEN[:mroot]:
            if prim.mod(zahl):
                break;
        else:
            PRIMZAHLEN.append(prim_modulo(zahl))
            # Wenn n > 0 eine bekannte Natuerlichezahl und sqrt(n) bekannt ist, gibt es einen O(1) Weg um sqrt(n+1) zu berechnen ?.
            mroot = int(len(PRIMZAHLEN) ** 0.5)

    return [2] + [ m.teiler for m in PRIMZAHLEN ]
PS: Der Code ist von mir, das alternative Modulo Verfahren hab ich aber gestern Abend irgendwo im Internet gelesen, ich werde es heute Abend mal Verlinken.

PPS: Optimierungen an der prim_modulo Klasse sind willkommen.
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Okok .. einen, noch einen...

Das Teilerverfahren erlaubt es uns den Fermat-Pseudoprimzahl Test als Vorfilter anzuwenden.

Der Fermat-test erkennt zwar alle Primzahlen, nennt aber auch einige wenige nicht Primzahlen. (False-Positivs) Daher kann der Fermattest den Teilertest nicht ablösen, diesen aber als Vorstufe geschaltet werden.

Der Fermatsatz:

Wähle für a eine beliebige natürliche Zahl (>1) die kein Teiler von n ist. Da wir nur ungerade Zahlen Testen, bietet es sich an a=2 zu wählen.
Die Zahl 'n' ist pseudoprim wenn für folgende diskrete Exponentialfunktion gilt:

Code: Alles auswählen

a**(n-1) % n == 1
Da komm es sehr bequem das Pythons 'pow' Syntax die diskrete Exponentialfunktion mit abdeckt. (Muss man auch erst mal wissen)
Anstatt

Code: Alles auswählen

 b **e % m
kann auch gleich

Code: Alles auswählen

pow(b,e,m)
geschrieben werden.

Auf unser Primzahlenproblem angewendet bedeutet es:

Code: Alles auswählen

if pow(2,Zahl-1, Zahl) != 1:
   print ("%s ist keine Primzahl" % Zahl)
Benchmarkmäßig bringt dies unter Python2 & Python3 knapp 10% mehr Prim für unserer Laufzeit. (bei größeren Zahlen eventuell mehr) Pypy zeigt mal wieder das der Compiler besser optimiert, als selbst die complexeren Bibliotheks und gar Buildinfunktionen; mit Fermat läufts unter pypy 100% langsamer wie ohne.

Quellen:
* https://de.wikipedia.org/wiki/Fermatscher_Primzahltest
* https://de.wikipedia.org/wiki/Diskrete_ ... alfunktion
* http://www.math.umn.edu/~garrett/crypto ... ython.html
Üpsilon
User
Beiträge: 225
Registriert: Samstag 15. September 2012, 19:23

Ehrlich gesagt blicke ich den Zweck der Klasse prim_modulo nicht.
Was spricht gegen ne einfache Funktion?

Code: Alles auswählen

def ist_teilbar(dividend, divisor):
    zwischensumme = divisor
    while True:
        if zwischensumme == dividend: return True
        if zwischensumme > dividend: return False
        zwischensumme += divisor
Und was ist daran besser als Modulo zu benutzen?
Und wieso benennst du Variablen in verschiedenen Sprachen?
PS: Die angebotene Summe ist beachtlich.
BlackJack

@Üpsilon: Modulo bedeutet da wird eine Division durchgeführt, was grundsätzlich erst einmal aufwändiger ist als Addition.
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

@Üpsilon: der "Trick" bei dem Verfahren ist, daß man die Teilbarkeit nicht jedesmal von vorne prüft, wie in Deiner Funktion, sondern daß alle Primzahlen schon in einem Vielfachen vorliegen, das ungefähr schon so groß ist, wie die Zahl, die man gerade prüfen will. Daher muß man für jede Primzahl nicht nur die Zahl an sich, sondern auch ihr Vielfaches speichern und daher die Klasse.
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Und wieso benennst du Variablen in verschiedenen Sprachen?
Geh mal davon aus das es dafür keinen echten Grund gibt.
Was spricht gegen ne einfache Funktion?
Deine einfache Funktion muss jedes mal alle vielfachen der Primzahl durch addieren, das ist nicht sehr effizient.
Das prim_modulo Objekt einer Primzahl speichert das Vielfache der letzte getesteten Zahl damit es nicht von vorne hochzählen muss.
Das funktioniert weil die Zahlen bei diesem Problem in aufsteigender Reihung ( Zahl[n+1] > Zahl[n] ) getestet werden und damit das Vielfache im prim_mod Objekt immer aktuell ist und niemals zurück gesetzt werden braucht.
Und was ist daran besser als Modulo zu benutzen?
Das Modulo ist eine Division und je nach CPU 33 bis 100 mal langsamer als eine einfache Addition oder Subtraktion. (Ausnahmen sind die Divisionen durch das vielfachen von 2, die können über eine Bitschiebe Operation abgebildet werden.)

Bei prim_mod werden nicht mehr als eine Handvoll Additionen/Subtraktionen benötigt.
Leider kommt der Effekt unter CPython nicht zum Tragen da die Reibungsverluste durch den Interpreter hier zu gross sind.

Quelle zu den Cycles der CPU Operationen:
https://gmplib.org/~tege/x86-timing.pdf
BlackJack

@pyHoax: Deine modulofreie Variante mal als Generatorfunktion (ungetestet):

Code: Alles auswählen

from itertools import count, islice


class PrimModulo(object):

    def __init__(self, teiler):
        self.teiler = teiler
        self.vielfaches = teiler + teiler
 
    def teilbar(self, zahl):
        while True:
            if zahl < self.vielfaches:
                return False
            elif zahl == self.vielfaches:
                return True
            self.vielfaches += self.teiler


def prim_teiler_ohne_modulo():
    yield 2
    primzahlen = list()
    wurzel = 0
    for zahl in count(3, 2):
        if not any(prim.teilbar(zahl) for prim in islice(primzahlen, wurzel)):
            primzahlen.append(PrimModulo(zahl))
            wurzel = int(len(primzahlen)**0.5)
            yield zahl
Der Vorteil von diesen Ansätzen ist ja gerade das sie im Gegensatz zum Sieb keine Obergrenze im voraus kennen müssen, also würde ich die auch nicht beschränken.
Antworten