Klassenvorstellung: Primzahlen



  • if( ! (prime = (currentNumber % *it) ! = 0))



  • Auweh, hätte ich fast übersehen...

    primzahlen[2] = 42;
    std::cout << "Die 2. Primzahl ist " << primzahlen[2] << ".\n";
    std::cout << "Ne, ehrlich, ist so.\n";
    


  • hustbaer schrieb:

    Ich finde die Mischung aus "Iterator" (operator ++, --, 😉 und "Container" (operator []) seltsam. Würde ich so nicht machen.

    Weiters sind deine Inkrement und Dekrement Operatoren etwas eigenwillig indem sie etwas anderes als eine Kopie von bzw. Referenz auf *this zurückgeben. Würde ich ebenfalls so nicht machen.

    Wie würdest du es machen? Wie ja gesagt, mir fehlt die Übung.

    hustbaer schrieb:

    Das statische Member storedPrimes ist problematisch was Thread-Safety angeht.

    Selbe Sache.

    hustbaer schrieb:

    Der "is prime" Loop gehört in eine eigene Funktion. Zumindest wenn du diesen naiven Algorithmus verwendest (der alles andere als schnell ist). Dadurch sparst du dir die Hilfsvariable und das break .

    Ich habe das jetzt ausgelagert. Mir ist leider kein andere Algorithmus eingefallen!

    hustbaer schrieb:

    Der Sinn des 2. Template-Parameters ist mir auch nicht ganz klar. Wieso sollte man wollen dass die Klasse etwas anderes als std::vector verwendet? Und unnötigerweise lass ich meinen Klassen nicht vorschreiben was sie intern zu verwenden haben.

    Wer weiß. Vieleicht hat der Nutzer einen effiktiveren Container zur Verfügung, oder möchte einfach einen anderen verwenden. Macht std::vector ja genau so. (mit dem allocator)

    hustbaer schrieb:

    Genaugenommen ist mir sogar der Sinn des 1. Template-Parameters nicht ganz klar, da es mMn. wenig Sinn macht etwas anderes als size_t für die Primzahlen selbst zu verwenden. Auf 32 Bit Systemen sind das 4 Byte und der Maximale Wert ist ~~ 4 Mrd. Bis 4 Mrd. gibt es so Pi * Daumen 180 Mio. Primzahlen. Bei 4 Byte pro Zahl macht das 720 MB. Das liegt schon in der Grössenordnung wo man auf 32 Bit Systemen anfängt sich Sorgen um std::bad_alloc zu machen (die 720 MB müssen ja am Stück verfügbar sein!).
    Und auf 64 Bit Systemen geht dir sowieso der Speicher aus bevor size_t überläuft.

    Wenn jemand z.B. eine eigene Zahlenklasse oder ähnliches hat, oder einfach einen kleineren Bereich haben will. Wer weiß.
    (P.S.: Ich war selbst schon mal in solch einer Situation, in der ich eine Templateklasse hätte besser brauchen können)
    (P.P.S.: Ich persönlich kenne nämlcih zwei recht spezielle Klassen. Die eine ist eine Zahlenklasse, die weit über jeden normalen Datentyp herausgeht. Die andere ist eine Containerklasse, die wenn sie zu viel RAM verbraucht gezielt auf die Festplatte auslagert. Somit kann sie deutlcih mehr als der RAM speichern.)

    hustbaer schrieb:

    Und der Sinn deiner Klasse überhaupt ist mir nicht so ganz klar. Also mir fällt jetzt kein Anwendungsfall ein wo ich die verwenden wollen würde. 😉

    Primzahlen... Wer braucht die nicht??? (Primzahlen, fand ich halt schon immer interessant. Und ich habe mir einen kleinen Primfaktorenzerleger programmiert. Da verwende ich die Klasse)

    hustbaer schrieb:

    Auweh, hätte ich fast übersehen...

    primzahlen[2] = 42;
    std::cout << "Die 2. Primzahl ist " << primzahlen[2] << ".\n";
    std::cout << "Ne, ehrlich, ist so.\n";
    

    Habs gefixt! habe ja ganz vergessen, dass man die Refferenzen const machen muss/sollte!

    P.S.: Danke für die konstruktive Kritik und die Verbesserunsgvorschläge.
    Ich habe außerdem die Klasse im Startpost aktualiesiert!



  • wenig Sinn macht etwas anderes als size_t für die Primzahlen selbst zu verwenden. Auf 32 Bit Systemen sind das 4 Byte und der Maximale Wert ist

    Ich haette auch gern auf 32 Bit Systemen 64 Bit Primzahlen. Fuer einzelne verwende ich dort auch uint64_t.

    Bei 4 Byte pro Zahl macht das 720 MB

    Unter Verwendung von prime wheels und Bitsets kann der Speicherbedarf drastisch reduziert werden.

    Tja zur Klasse ... ich mach es anders. Hierarchisch:
    1.) prime_wheel - Spezifierziert das Pimzahlsieb zur Erzeugung von Primzahlen/Sieben
    2.) primes - Container mit allen Primzahlen in einer bestimmten Range (echt vorhanden, Speicherbedarf O(n) )
    3.) prime_sieve - Pseudocontainer fuer Primzahlen in bestimmter Range (nicht echt vorhanden, ezeugt beim Iterationsprozess, Speicherbedarf O(k), wobei k fix im Bereich des L1-Caches liegt)

    Alle beiden Containertypen liefern Iteratoren um mit den Primzahlen zu arbeiten.

    Btw. deine is isPrime-Funktion ist falsch. Eine gegebene Zahl kann durch Primzahlen ausserhalb deines Containers teilbar sein.



  • knivil schrieb:

    Btw. deine is isPrime-Funktion ist falsch. Eine gegebene Zahl kann durch Primzahlen ausserhalb deines Containers teilbar sein.

    Nicht in dem Zusammenhang, in dem ich sie aufrufe. Ich prüfe ja alle ungeraden Zahlen der Reihe nach. Also sind (wenn ich die Funktion aufrufe) alle Primzahlen, die kleiner als die Zahl sind, vorhanden.

    EDIT:

    Ich habe die Performance der isPrime -Funktion erhöht.



  • hustbaer schrieb:

    Ich finde die Mischung aus "Iterator" (operator ++, --, 😉 und "Container" (operator []) seltsam. Würde ich so nicht machen.

    RandomAccessIteratoren brauchen operator[].



  • knivil schrieb:

    wenig Sinn macht etwas anderes als size_t für die Primzahlen selbst zu verwenden. Auf 32 Bit Systemen sind das 4 Byte und der Maximale Wert ist

    Ich haette auch gern auf 32 Bit Systemen 64 Bit Primzahlen. Fuer einzelne verwende ich dort auch uint64_t.

    Bei 4 Byte pro Zahl macht das 720 MB

    Unter Verwendung von prime wheels und Bitsets kann der Speicherbedarf drastisch reduziert werden.

    Ich meinte das schon im Zusammenhang mit seiner Implementierung.



  • Nathan schrieb:

    hustbaer schrieb:

    Ich finde die Mischung aus "Iterator" (operator ++, --, 😉 und "Container" (operator []) seltsam. Würde ich so nicht machen.

    RandomAccessIteratoren brauchen operator[].

    Die liefern aber nicht das zurück was sein operator[] zurückliefert.



  • hustbaer schrieb:

    Ich meinte das schon im Zusammenhang mit seiner Implementierung.

    Ja, die Antwort war auch eher dayu gedacht, um yu yeigen, wo die Reise hingehen sollte.

    Ansonsten:

    #include "Primes.cpp"

    Bitte nenne die Datei "Primes.impl" oder "Primes.incl" oder ... . Die Endung cpp wird von den meisten IDEs automatisch als Uebersetzungseinheit betrachtet.



  • Die erinnerung schrieb:

    Habs gefixt! habe ja ganz vergessen, dass man die Refferenzen const machen muss/sollte!

    Ich würde die eher einfach by-value zurückgeben.

    Die erinnerung schrieb:

    hustbaer schrieb:

    Und der Sinn deiner Klasse überhaupt ist mir nicht so ganz klar. Also mir fällt jetzt kein Anwendungsfall ein wo ich die verwenden wollen würde. 😉

    Primzahlen... Wer braucht die nicht??? (Primzahlen, fand ich halt schon immer interessant. Und ich habe mir einen kleinen Primfaktorenzerleger programmiert. Da verwende ich die Klasse)

    Ich brauch praktisch nie Primzahlen.

    Und wenn doch, dann eher eine Funktion der ich eine Zahl geben kann, die mir die nächste Primzahl die >= oder <= dieser Zahl ist zurückgibt.

    Aber für "gib mir die ersten N Primzahlen" hatte ich bisher noch nie bedarf. Ausgenommen bei diversen "Programmieraufgaben" wo genau das gefragt war.



  • Hallo,

    Kritik hast Du ja schon reichlich geerntet, also ich finde so eine Klasse gut! Gut deshalb weil man das ganze Brimborium mit den Primzahlen hinter dieser Schnittstelle gut unterbringen kann.
    Das muss ich sogar gut finden, da ich mir vor Jahren selbst so ein Ding gebaut habe 😉

    hustbaer schrieb:

    Und der Sinn deiner Klasse überhaupt ist mir nicht so ganz klar. Also mir fällt jetzt kein Anwendungsfall ein wo ich die verwenden wollen würde. 😉

    liegt wohl daran, dass Du noch nie ein Problem aus projecteuler.net bearbeitet hast.

    Zwei Dinge rufen dann doch nach Verbesserungsbedarf.
    1.) Du kannst deutlich an Performance gewinnen, wenn Du bei der Suche nach einem möglichen Teiler nur bis <=sqrt(number) statt bis number läuft. Dies macht sich umso stärker bemerkbar, je größer die Primzahlen werden.
    2.) Die Funktion zur Bestimmung ob eine Zahl eine Primzahl gehört isoliert - hast Du schon gemacht.

    2a) statt einer Methode IsPrime würde ich hier eine freie Funktion verwenden, die intern wieder ein Objekt der Klasse Primes nutzt. So kann die Funktion auch außerhalb der Klasse einfach und ohne Angabe der Template-Parameter genutzt werden.

    Bleibt noch zu erwähnen, dass aus der Sicht von C++ und der STL die Klasse Primes ein Iterator ist. Also sollte man es zu einen auch so benenne und zum anderen bietet es sich an, die Hilfen von boost zu nutzen. Siehe dazu den boost.iterator.adaptor.

    Ich finde es gut, wenn Du Deine Klasse hier zur Diskussion stellst.
    Gruß
    Werner



  • Werner Salomon schrieb:

    2a) statt einer Methode IsPrime würde ich hier eine freie Funktion verwenden, die intern wieder ein Objekt der Klasse Primes nutzt. So kann die Funktion auch außerhalb der Klasse einfach und ohne Angabe der Template-Parameter genutzt werden.

    Könntest du etwas konkreter werden? Ich verstehe leider nciht ganz, was du meinst.

    Werner Salomon schrieb:

    Bleibt noch zu erwähnen, dass aus der Sicht von C++ und der STL die Klasse Primes ein Iterator ist. Also sollte man es zu einen auch so benenne und zum anderen bietet es sich an, die Hilfen von boost zu nutzen. Siehe dazu den boost.iterator.adaptor

    Das werde ich mir mal anschauen. Dennoch könnte ein wenig Hilfe nicht schaden, da ich sowas noch nie gemacht habe!

    Außerdem danke für deine aufbauende Worte. Und für den Tipp mit der Wurzel. Ich hatte jetzt vorübergehend den zu überprüfenden Wert quadriert und mit der Zahl verglichen. Diese methode ist besonmders bei goßen Primzahlen deutlich effizienter. Habe ich auch in meinen Test festgestellt!



  • Die erinnerung schrieb:

    Werner Salomon schrieb:

    2a) statt einer Methode IsPrime würde ich hier eine freie Funktion verwenden, die intern wieder ein Objekt der Klasse Primes nutzt. So kann die Funktion auch außerhalb der Klasse einfach und ohne Angabe der Template-Parameter genutzt werden.

    Könntest du etwas konkreter werden? Ich verstehe leider nciht ganz, was du meinst.

    Einfach eine Funktion, in der die Klasse Prime wieder benutzt wird - also etwa so:

    // --   freie Funktion 'isPrime' liefert 'true', falls x eine Primzahl ist.
    template< typename T >
    bool isPrime( const T& x )
    {
        const T divisor_max = T(std::sqrt( double(x) )); // erfordert #include <cmath>
        for( Prime< T > i; *i <= divisor_max; ++i )
            if( x % *i == 0 )
                return false;
        return true;
    }
    

    .. und die benutzt Du dann wie gehabt in const T& Primes<T, Container>::operator[](size_t pos) . Eventuell musst Du noch einen Prototyp vor die Klasse schreiben.

    Gruß
    Werner



  • @Werner Salomon
    Das meinte ich ja: ausserhalb von diversen "Übungen" hab' ich noch nie Primzahlen gebraucht. Ich meinte mal welche zu brauchen als ich für ein Projekt nen eigenen Hashtable implementieren wollte. Hat sich dann aber - wie es meist der Fall ist - herausgestellt, dass es nicht sinnvoll/nötig ist hier was selbst zu basteln.

    Und sollte ich je welche brauchen, dann such ich mir ne passende Library, die dann auch entsprechend schnell ist 😉

    Werner Salomon schrieb:

    Bleibt noch zu erwähnen, dass aus der Sicht von C++ und der STL die Klasse Primes ein Iterator ist. Also sollte man es zu einen auch so benenne und zum anderen ...

    Naja, ist sie eben nicht. Sie tut bloss ein bisschen ähnlich wie ein Iterator. Das war ja auch einer meiner Kritikpunkte.

    Guck dir mal die Returnwerte der ++/-- Operatoren an und was der [] Operator macht...

    Und Funktionen die nicht Thread-safe sind sind mir generell ein Greul. Genau so wie Funktionen die ungefragterweise mächtig Speicher verbrauchen, und diesen nach Aufruf der Funktion nicht mehr freigeben. Beides ist hier gegeben. Dummerweise hab' ich keinen Vorschlag wie man das besser machen kann, ohne gleichzeitig die Verwendung wesentlich unparktischer zu machen...



  • hustbaer schrieb:

    Werner Salomon schrieb:

    Bleibt noch zu erwähnen, dass aus der Sicht von C++ und der STL die Klasse Primes ein Iterator ist. Also sollte man es zu einen auch so benenne und zum anderen ...

    Naja, ist sie eben nicht. Sie tut bloss ein bisschen ähnlich wie ein Iterator. Das war ja auch einer meiner Kritikpunkte.

    Guck dir mal die Returnwerte der ++/-- Operatoren an und was der [] Operator macht...

    Ja - ich bin absolut Deiner Meinung. Habe mich bloss falsch ausgedrückt.
    So eine Klasse wie Prime ist vom Konzept her ein Iterator und sollte sich natürlich auch so verhalten, dass heißt z.B. im operator++ ein return *this; und kein Value.

    hustbaer schrieb:

    Und Funktionen die nicht Thread-safe sind sind mir generell ein Greul.

    Thread-Safety ist im Kontext der Probleme aus Projecteuler i.A. schlicht überflüssig, und man könnte es relativ leicht hinzufügen.

    Gruß
    Werner



  • Werner Salomon schrieb:

    template< typename T >
    bool isPrime( const T& x )
    {
        const T divisor_max = T(std::sqrt( double(x) )); // erfordert #include <cmath>
        for( Prime< T > i; *i <= divisor_max; ++i )
            if( x % *i == 0 )
                return false;
        return true;
    }
    

    Ich finde die Idee echt genial. Ich habe sie in einer leicht modifizierten Version umgestzt!

    Wegen der Iteratordiskussion:

    Ich habe keinerlei Problem, bei den inkrementen und Dekrementen this zurückzugeben. Werde ich dann auch noch umsetzten.

    Zum Thema Threadsicherheit: Wie genau mache ich das?



  • Werner Salomon schrieb:

    hustbaer schrieb:

    Und Funktionen die nicht Thread-safe sind sind mir generell ein Greul.

    Thread-Safety ist im Kontext der Probleme aus Projecteuler i.A. schlicht überflüssig, und man könnte es relativ leicht hinzufügen.

    OK.
    Kommt halt drauf an was man haben will. Wenn man "nur" ein nettes Werkzeug für solcherlei Aufgaben will, dann OK.

    Andrerseits kann man bei den Projecteuler Aufgaben vermutlich auch gut damit leben irgendwo ne globale Instanz einer Klasse zu haben. Statt freie Funktionen würde man dann einfach Memberfunktionen dieser Klasse aufrufen. Und die Klasse könnte man dann leicht Thread-safe machen (im Sinn von "verschiedene Instanzen in verschiedenen Threads = OK, selbe Instanz in verschiedenen Threads = nicht OK" -- die "üblichen Regeln" halt).



  • Die erinnerung schrieb:

    Zum Thema Threadsicherheit: Wie genau mache ich das?

    Die einfachste Variante wäre in jeder Funktion die auf deine statische Membervariable zugreift eine (ebenso statische) Mutex zu locken.
    Dummerweise kostet das auch einiges an Performance. So ein Mutex Lock + Unlock ist nicht ganz billig.
    Im Vergleich dazu zig oder hunderte Primzahlen-Kandidaten durchzuspulen ist es vermutlich sehr billig.
    Im Vergleich dazu einfach nur in einen Vektor reinzugreifen und eine Zahl daraus zurückzugeben ist es vermutlich eher nicht so billig.



  • Werner Salomon schrieb:

    Thread-Safety ist im Kontext der Probleme aus Projecteuler i.A. schlicht überflüssig, und man könnte es relativ leicht hinzufügen.

    Autsch. Fürs ProjektEuler brauchste normalerweise auch keinen Cache der Primzahlen. Und falls doch, füllt man sich einfach vorher einen vector damit.

    Den Generator wie Iteratoren aussehen zu lassen, naja... 👎
    Da liegt auch das Problem mit der Thread-Safety.

    Dein Vorschlag, die IsPrime auszulagern, ach nöö, so aber nicht. Soll schon, wenn man schon alle Primzahlen speichern will, die Speicherung auch benutzen dürfen.



  • Die erinnerung schrieb:

    Werner Salomon schrieb:

    template< typename T >
    bool isPrime( const T& x )
    {
        const T divisor_max = T(std::sqrt( double(x) )); // erfordert #include <cmath>
        for( Prime< T > i; *i <= divisor_max; ++i )
            if( x % *i == 0 )
                return false;
        return true;
    }
    

    Ich finde die Idee echt genial. Ich habe sie in einer leicht modifizierten Version umgestzt!

    Aber wenn Du uns nicht auf dem Laufenden hältst, werden wir bald schrecklich aneinander vorbeireden.
    Wenn Du DIESE Version benutzt hast, wozu dann überhaupt noch die Primzahlen speichern? Weder Dein Primfaktorenprogramm noch die anderen Übungen brauchen, daß Du im Primzahlenfeld vorwärts und rückwärts laufen kannst, oder?


Anmelden zum Antworten