primzahlenrechner

  • Morgen liebe autoitler,

    das Thema gab es sicher schon 200 mal und es gab sicher auch schon 10 wettbewerbe dazu aber ich hatte mal wieder lust an etwas logik.
    Deswegen hier ein kleiner Primzahlenrechner.
    Komme damit bei 1Mio Zahlen auf ca 25 sekunden auf einer Win VM mit dualcore. Mich würde interesieren wie verändert sich die Leistung auf anderen CPU und gibt es scripte die noch bedeutend schneller sind?
    Ja mit dem sieb geht es unter Anderem schneller aber ich wollte es mal ohne versuchen. Und ich weis das Autoit keine schnelle Sprache ist und wenn man Primzaheln berechnen will nimmt man c und eine GPU geht mir nur um den Spaß am probieren und coden :thumbup:

  • Nicht schneller, sondern langsamer, aber dafür mit Spass!

    [autoit]

    ;primzahlen ab 3 mittels regex

    [/autoit][autoit][/autoit][autoit]

    $string = "1"
    For $i = 3 To 3000 Step 2
    $string &= "11"
    If Not StringRegExp($string, "^1?$|^(11+?)\1+$", 0) Then ;regex matched KEINE Primzahlen
    ConsoleWrite('$i = ' & $i & @CRLF)
    EndIf
    Next

    [/autoit]
  • Du musst dir mal C++ anschauen :) Da habe ich das gleiche mit 3 Sekunden geschafft :D
    EDIT: Wenn ich gerade nichts übersehe, dann kannste das #include <Array.au3> oben weglassen

    Spoiler anzeigen

    Überraschung!


    MfG Donkey

  • ja das include kann weg. hatte erst mit addarray gearbeitet aber das ist viel zu langsam. c und c++ sind einfach beide super nach an der hardware dran aber wenn dus schnell willst dann assembler und fertig ^^

    edit: so bin jetzt am heim lappi und komme auf knapp 20 Sekunden ohne es zu kompilieren. liegt also an der vm. denke mit feinarbeit kommt man auf >10 aber das wird zu nachdenkintensiv ^^

  • Erklärung des RegEx: ^1?$|^(11+?)\1+$

    ^1?$ erster Teil findet Leerstring oder eine eins

    ^ start string
    1? eine eins, null oder ein Mal
    $ ende string

    | oder (zweiter teil) ^(11+?)\1+$
    ^ start string
    (11+?) Gruppe, welche 11 ein- oder mehrmals findet, diese Gruppe wird mit
    \1 "gespeichert"
    + ein oder mehrmals
    $ ende string


    Durch das "backtracken" der RegEx-Engine entstehen bei beispielsweise 9 einsen folgende "Gruppen"
    111111111
    es wird mit Zweiergruppen 11 angefangen
    11 11 11 11 die "übrige" eins matcht die Gruppe nicht, also backtrack mit 111
    111 111 111 Gruppen matchen, also KEINE Primzahl

    elf
    11111111111
    11 11 11 11 11 die "übrige" eins matcht die Gruppe nicht, also backtrack mit 111
    111 111 111 die übrige 11 matched nicht, also backtrack mit 1111
    1111 1111 die übrige 111 matched nicht, also backtrack mit 11111
    11111 11111 die übrige 1 matched nicht, backtrack zuende, KEIN MATCH -> Primzahl
    11111111111

  • Die Idee ist ja der Hammer.
    Das ist ja wirklich genial :thumbup:

    @GegX
    Auf meinem i5 M560 dauerts 23s für ne Million.

    @ProgrammingDonkey
    Zeig mal deinen C++-Code.
    Ist das der selbe Ansatz wie GegX's?
    Dann wäre das ja nur gerade mal um den Faktor 7 schneller als die AutoIt-Variante.
    Da wird irgendwas schief gelaufen sein - zu erwarten wäre eine deutlich höhere Beschleunigung.

    c und c++ sind einfach beide super nach an der hardware dran aber wenn dus schnell willst dann assembler und fertig

    Wenn du schnelleren Assemblercode schreibst als ein aktueller C/C++-Compiler raushaut dann solltest du unbedingt mal an die Frische Luft gehen - du sast definitiv zu lange vorm PC. ;)

  • Das war mein erstes und nahezu einziges C++ Script :D

    Spoiler anzeigen
    Spoiler anzeigen

    Überraschung!


    MfG Donkey

    Einmal editiert, zuletzt von ProgrammingDonkey (24. März 2015 um 18:29)

  • Servus, ich war gerade dabei die Aufgabe 3 (deutsche Übersetzung) in den Project Eulers zu bewerkstelligen. Es ging um Primfaktorzerlegung, da dies ganz gut zum Thema passt würde ich gerne das Skript hier veröffentlichen. Aufgrund der hohen Zahl musste ein effizienter Algorithmus her ~ Naja ^^

    Primfaktorzerlegung
    [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    #include <Array.au3>
    #include <MsgBoxConstants.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $aPrimeFactors = GetPrimeFactors(600851475143)

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox($MB_TOPMOST, 'Problem 3 - Largest prime factor', $aPrimeFactors[$aPrimeFactors[0]])

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    Func GetPrimeFactors($iNumb)
    Local $aPrimes[] = [2, 2, 3]
    Local $aPrimeFactors[1]
    Local $iC

    [/autoit] [autoit][/autoit] [autoit]

    While $iNumb <> 1
    $iC = 1
    While Mod($iNumb, $aPrimes[$iC])
    $iC += 1
    If $iC > $aPrimes[0] Then AddNextPrime($aPrimes)
    WEnd
    $iNumb /= $aPrimes[$iC]
    _ArrayAdd($aPrimeFactors, $aPrimes[$iC])
    $aPrimeFactors[0] += 1
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    _ArraySort($aPrimeFactors, 0, 1)
    Return $aPrimeFactors
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func AddNextPrime(ByRef $aPrimes)
    Local $iNumb = $aPrimes[$aPrimes[0]]
    Local $i

    [/autoit] [autoit][/autoit] [autoit]

    While True
    $iNumb += 2
    For $i = 1 To $aPrimes[0] -1
    If Not Mod($iNumb, $aPrimes[$i]) Then ContinueLoop(2)
    Next
    _ArrayAdd($aPrimes, $iNumb)
    $aPrimes[0] += 1
    Return
    WEnd
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit]

    Einmal editiert, zuletzt von Yjuq (29. März 2015 um 01:58)

  • Da ich ja gerade in Java unterwegs bin, habe ich den Code von @Andy mal in einem Java-Programm untergebracht:


    Falls sich jemand wundert, warum in Zeile 17 das "\" durch "\\" ersetzt wurde: In Java muss das "\" um als "\" zu funktionieren auch ausgeklammert werden.
    Bei 10.000 Primzahlen schafft er es innerhalb von - sage und schreibe - 6 Sekunden...

    Es gibt sehr viele Leute, die glauben. Aber aus Aberglauben.
    - Blaise Pascal

  • Dann wäre das ja nur gerade mal um den Faktor 7 schneller als die AutoIt-Variante.

    Tja, auch der beste und schnellste Compiler kann aus Sch**** kein Gold machen.
    Ich bin allerdings davon überzeugt, dass die Jungs und Mädels in Santa Clara schon (wenn nicht bereits implementiert) Code vor dem compilieren analysieren um herauszufinden was der Programmierer BEABSICHTIGT!
    Dabei würde bei der Analyse das o.g. Programms festgestellt, dass mit unveränderlichen Daten (Zahlen) gearbeitet wird und dass die durch 2 teilbaren Zahlen niemals in die "Zielgruppe" passen. Somit würde der Compiler den Code so weit optimieren, dass aus

    Code
    for (int i = 2; i < sqrt(float(iNumber)); i = i+1){


    eben

    Code
    for (int i = 1; i < sqrt(float(iNumber)); i = i+2){


    wird. Faktor 2 in der Geschwindigkeit, voila!

    Ich hatte neulich einen Bericht dahingehend gelesen, dass diese "optimierten" Codestellen im orginalen Quellcode ersetzt und markiert wurden. Einige gestandene Programmierer hatten nicht schlecht gestaunt, wie viel von ihrem Code "wegoptimiert" werden KANN!
    Daher auch die grundlegende Direktive, möglichst wenig selbst zu programmieren, sondern bereits bestehende (und somit OPTIMIERTE! ) Bibliotheken zu verwenden.
    Das Problem tut sich aber auf, dass es Bibliotheken gibt, die irgendjemand irgendwann erstellt, aber niemand sonst sich die Mühe gemacht hat, diese zu optimieren! Aber was solls, funktioniert doch!!!
    Von daher befindet sich der Kollege ProgrammingDonkey in der schönen Lage, auch "was solls, funktioniert doch!"

    Da wird irgendwas schief gelaufen sein - zu erwarten wäre eine deutlich höhere Beschleunigung.

    unter dieses Statement zu setzen :D

  • Optimierung sollte halt immer auf Algorithmenebene beginnen.
    In dem Fall wäre es erst einmal sinnvoll den Algorithmus von GegX zu übertragen.
    Das wurde in deinem C++-Programm nämlich nicht gemacht.

    Das Primzahlen nur ungerade sein können (außer der 2) hat Andy dir bereits erzählt.

    In deinem Programm wird zur Überprüfung auf eine Primzahl durch alle natürlichen Zahlen kleiner Wurzel der zu prüfenden Zahlen geteilt. Es geht aber noch deutlich effektiver: Jede natürliche Zahl ist durch eine Kombination von Primzahlen teilbar (siehe Primfaktorzerlegung). Wenn sie selbst eine Primzahl ist, so ist ihr Primfaktor schlicht die Zahl selbst. GegX' nutzt das in seinem Skript um die Zahl der nötigen Vergleiche nochmals deutlich zu reduzieren: indem er die Zahl nur gegen die bisher gefunden Primzahlen checkt (und das auch nur bis Wurzel(n)). Somit kann er die Anzahl der nötigen Vergleiche drastisch verringern.
    Es wäre also durchaus clever die Primzahlen zwischenzuspeichern. Da du C++ verwendest, suchst du also nach einer Datenstruktur bei der dynamisch Elemente ans Ende hinzugefügt werden können und die schnell durchiteriert werden kann. Mein Vorschlag hierfür wäre die std::deque.

    Das was dem Programm den Rest in Sachen Performance gibt, ist die Ausgabe aller Zahlen auf die Konsole. Entferne mal zum Spaß die couts und lass dir nur die letzte Zahl ausgeben - es sollte nun drastisch schneller laufen

  • GegX:
    Dein Algorithmus hat eine Ausführungszeit von 25 Sekunden bei mir. Ich habe mal versucht mithilfe des Sieb des Eratosthenes die benötigte Zeit zu verringert. Alle Primzahlen von 2 bis 1000000 werden bei mir in 18~19 Sekunden ermittelt. Da geht bestimmt noch mehr:

    Spoiler anzeigen
    [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    #include <Array.au3>
    #include <MsgBoxConstants.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $iNumb = 1000000
    Global $hTimer, $aPrimes

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    $hTimer = TimerInit()
    $aPrimes = SieveOfEratosthenes($iNumb)
    ConsoleWrite('Time => ' & TimerDiff($hTimer) & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    _ArrayDisplay($aPrimes)

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    Func SieveOfEratosthenes($iNumb)
    Local $aPrimes[Ceiling($iNumb / 2) +1] = [1, 2]
    Local $i, $n, $aFindAll

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 3 To 2 * UBound($aPrimes) -2 Step 2
    $aPrimes[0] += 1
    $aPrimes[$aPrimes[0]] = $i
    Next

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 2 To Sqrt($aPrimes[0])
    If Not $aPrimes[$i] Then ContinueLoop

    [/autoit] [autoit][/autoit] [autoit]

    For $n = $i + $aPrimes[$i] To $aPrimes[0] Step $aPrimes[$i]
    $aPrimes[$n] = False
    Next
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $aFindAll = _ArrayFindAll($aPrimes, False)
    _ArrayInsert($aFindAll, 0, UBound($aFindAll))
    _ArrayDelete($aPrimes, $aFindAll)
    $aPrimes[0] = UBound($aPrimes)

    [/autoit] [autoit][/autoit] [autoit]

    Return $aPrimes
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit]
  • ja das sieb ist am anfang super langsam aber je länger es geht umso besser wird es. aber wieder mal sehr spannend und zu sehen wie andere Sprachen das Problem lösen.

  • Klar geht das schneller: Alle Primzahlen bis 1'000'000 in 1,5 Sekunden ^^

    Spoiler anzeigen
    [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global Const $hTime = TimerInit()
    Global Const $aPrimes = SieveOfEratosthenes(1000000)
    ConsoleWrite(Round(TimerDiff($hTime) / 1000, 3) & ' Sekunden' & @CRLF)
    _ArrayDisplay($aPrimes)

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit] [autoit][/autoit] [autoit]

    Func SieveOfEratosthenes(Const $iLimit)
    Local $iUBound = Floor(($iLimit -1) / 2)
    Local $i, $aSieve[$iUBound +1], $j, $iC, $iT, $aPrimes

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To Floor(Sqrt($iLimit) -1) / 2
    If Not $aSieve[$i] Then
    For $j = 2 * $i * ($i +1) To $iUBound Step 2 * $i +1
    If Not $aSieve[$j] Then
    $aSieve[$j] = True
    $iC += 1
    EndIf
    Next
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $iT = $iC

    [/autoit] [autoit][/autoit] [autoit]

    Dim $aPrimes[$iUBound - $iC +1] = [2]
    For $i = 1 To $iUBound
    If Not $aSieve[$i] Then
    $aPrimes[$iT - $iC +1] += 2 * $i +1
    $iC -= 1
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit]

    Return $aPrimes
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit]
  • Genial :)
    Allerdings verstehe ich deinen Code nicht ganz, und bitte dich darum mal eine Erklärung dazu zu verfassen :)
    Und noch eine Idee:

    Spoiler anzeigen


    Eine nicht-Primzahl ist doch zwangshaft durch eine Primzahl teilbar, oder?
    Dann hätte ich da noch eine Idee:

    Spoiler anzeigen

    [autoit#include <Array.au3> $hTimer = TimerInit() Global Const $iMax = 100000 $aResult = GetPrimes($iMax) MsgBox(0,"Done","Passed time:"&@TAB&Round(TimerDiff($hTimer)/1000, 4)) _ArrayDisplay($aResult) Func GetPrimes($iHead) $aPrimes[1] = [2] $bContinue = True For $iNumber = 3 to $iHead step +2 For $iElement in $aPrimes If $iElement > sqrt($iNumber) Then ExitLoop EndIf If Mod($iNumber, $iElement) = 0 Then $bContinue = False ExitLoop EndIf Next If not $bContinue Then $bContinue = True Else _ArrayAdd($aPrimes, $iNumber) EndIf Next Return $aPrimes EndFunc[/autoit]


    Ist zwar leider sehr langsam, aber daran arbeite ich noch; Wäre jedenfalls auch ein Ansatz.

    Spoiler anzeigen

    Überraschung!


    MfG Donkey

    Einmal editiert, zuletzt von ProgrammingDonkey (2. April 2015 um 20:30)

  • Ich habe mich nun an die gleiche Technik angelehnt, wie @Make-Grafik, allerdings funktioniert das ganze nur sehr langsam:

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>
    Const $iMax = 3000
    $hTimer = TimerInit()
    $aPrimes = GetPrimes($iMax)
    ConsoleWrite(@CRLF&"Time >> "&round(TimerDiff($hTimer)/1000, 4)&@CRLF)
    _ArrayDisplay($aPrimes)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func GetPrimes(Const $iHead)
    Local $aPrimes[round($iHead/2-1, 0)]
    $x = 0
    for $i = 3 to $iHead step +2
    $aPrimes[$x] = $i
    $x += 1
    Next
    $Stop = False
    for $x = 0 to UBound($aPrimes)-1 step +1
    If $x > UBound($aPrimes)-1 or $Stop Then
    ConsoleWrite("Last one: "&$iNumber&@CRLF)
    ExitLoop
    EndIf
    $Stop = True
    $iNumber = $aPrimes[$x]
    Local $hTimer = TimerInit()
    for $iNum = UBound($aPrimes)-1 to $x+1 step -1
    If Mod($aPrimes[$iNum], $iNumber) = 0 Then
    $Stop = False
    ConsoleWrite("Delete: "&$aPrimes[$iNum]&@CRLF)
    _ArrayDelete($aPrimes, $iNum)
    EndIf
    Next
    ConsoleWrite($iNumber&" / "&UBound($aPrimes)-1&" Time: "&TimerDiff($hTimer)&@CRLF)
    Next
    Return $aPrimes
    EndFunc

    [/autoit]


    Wie kann ich da (viel) mehr Zeit sparen?

    Spoiler anzeigen

    Überraschung!


    MfG Donkey