Seite 3 von 3

Re: Was ist falsch an meinem code?

Verfasst: Dienstag 9. Juli 2013, 21:20
von snafu
Wie das so ist mit nicht-trivialen Optimierungen: Sie mögen nötig sein wegen der Verarbeitungzeit für große Eingaben, aber man steigt auch immer schlechter durch den Code. ;)

Jetzt scheint (auf dem ersten Blick) aber alles ok zu sein. :)

Re: Was ist falsch an meinem code?

Verfasst: Dienstag 9. Juli 2013, 21:26
von jerch
snafu hat geschrieben:Wie das so ist mit nicht-trivialen Optimierungen: Sie mögen nötig sein wegen der Verarbeitungzeit für große Eingaben, aber man steigt auch immer schlechter durch den Code. ;) ...
Liegt wohl eher am Hinrotzen :oops:

Re: Was ist falsch an meinem code?

Verfasst: Dienstag 9. Juli 2013, 21:57
von Sirius3
Ganz schön anstrengend, nochmals einen Faktor 5 in der Geschwindigkeit rauszuholen:

Code: Alles auswählen

def prime_factors(num):
    result=[]
    for k in (2,3,5):
        while num%k==0: num //= k; result.append(k)
    sqn=int(num**0.5)
    k=7
    while k<=sqn:
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 4
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 2
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 4
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 2
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 4
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 6
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 2
        while num%k==0: num //= k; sqn = int(num**0.5); result.append(k)
        k += 6
    if num!=1:
        result.append(num)
    return result

Re: Was ist falsch an meinem code?

Verfasst: Mittwoch 10. Juli 2013, 14:32
von jerch
Mehr dürfte mit Python und dieser Methode (wheel factorisation) kaum drin sein. Nimmt man die 7 mit hinzu, kann man zwar mehr eliminieren, allerdings ist das Pattern dann schon 210 groß und der Gewinn marginal (77% vs. 73% für 2*3*5). Die Eliminationrate wächst nur gering, während die 'wheels' riesig werden.

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 00:27
von Gwunderi
Hallo zusammen,

Hier noch mein Programm zur allgemeinen Erheiterung:
Wenn man Glück hat, kann es die Primfaktoren auch von 12-stelligen Zahlen in Sekundenschnelle finden, sogar für eine 15-stellige Zahl (679555042947243) braucht es keine halbe Minute, oder für die 18-stellige (879679555042947243) etwa 2 Minuten. Glück hat man, falls n / (Produkt der Primfaktoren bis 1 Mio.) kleiner als etwa 10'000 ist.
Es ist mir bewusst, dass man ein Programm nicht so schreibt, auch formal nicht : )

Das Programm von Sirius werde ich bald einmal versuchen nachzuvollziehen (geht glaube ich so vor: jedesmal wenn ein Primfaktor von n gefunden wurde, wird n durch diesen Faktor dividiert ... aber weiter sehe ich es im Moment nicht.)

Viel Spass und Grüsslein
Gwunderi

Code: Alles auswählen

primteiler = [2]        

n = int(input("Gib eine grosse Zahl ein: "))

a = 3

while a <=1000000:
        if all(a%x > 0 for x in primteiler) and n%a == 0:
                primteiler.append(a)
        a = a + 2

if n%2 > 0:
        primteiler.remove(2)
print("Erste Primfaktoren bis 1'000'000: ",primteiler)                          

# nur falls grösster gefundener Primfaktor < n**0.5:

if primteiler[-1] < n**0.5:        
        
        p = 1
        for i in primteiler:
            p *= i
        print("Produkt Primfaktoren bis 1'000'000 = ", p)

        print("n durch Produkt: ",n/p)  

        if n/p == 1:
            print("Das waren alle Primfaktoren von n")
        else:
            if n/p < 10000000:
                print("Die restlichen kommen gleich.")
            if n/p > 10000000 and n/p < 100000000:
                print("Es könnte einige Minuten dauern.")
            if n/p > 100000000:
                print("Dauert zu lange - siehe Programm von Sirius")

            a = 3
        
            while a <= n/p:
                if all(a%x > 0 for x in primteiler) and n%a == 0:
                    primteiler.append(a)
                a = a + 2

            print(primteiler)

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 04:00
von BlackJack
@Gwunderi: Dein Hauptproblem, abgesehen von den vielen Sonderfällen die da unnötigerweise unterschieden werden, ist dass Du `n` nicht verkleinerst wenn Du einen Faktor gefunden hast. Dadurch testest Du deutlich mehr Teiler als nötig.

Und Du erzeugst dadurch auch mehr Primzahlen als nötig. Das kann man auf das Nötigste begrenzen in dem man das Programm so schreibt, dass jede gefundene Primzahl sofort nach dem sie gefunden wurde, verwendet wird um `n` zu verkleinern.

In funktionalen Programmiersprachen (die nicht „lazy” sind, wie Haskell) kann man das über eine Rückruffunktion lösen. An dieser Stelle möchte ich mich für meine JavaScript-Lösung entschuldigen, die umständlicher geschrieben ist, als das üblich wäre. Hier eine typischere und einfacher formulierte Variante für JavaScript:

Code: Alles auswählen

#!/usr/bin/env node
'use strict';
var _ = require('underscore');

Number.prototype.divides = function(n) { return n % this == 0; };

var primes = function (callback) {
    var primes = [];
    var candidate = 3;
    var testCandidate = function (prime) { return !prime.divides(candidate); };
    var stop = callback(2);
    while (!stop) {
        if (_(primes).every(testCandidate)) {
            primes.push(candidate);
            stop = callback(candidate);
        }
        candidate += 2;
    }
};

var factor = function (n) {
    var result = [];
    primes(function (prime) {
        if (prime.divides(n)) {
            result.push(prime);
            while (n > 1 && prime.divides(n)) n /= prime;
        }
        return n == 1;
    });
    return result;
};

var main = function () { console.log(factor(parseInt(process.argv[2]))); };

main();
In objektorientierten Sprachen gibt es in der Regel das Konzept von Iteratoren, also Objekte mit einer Methode, welche das nächste Element von etwas liefern oder erzeugen. Das könnte so aussehen (CoffeeScript):

Code: Alles auswählen

#!/usr/bin/env coffee

Number::divides = (n) -> n % this == 0


class PrimeGenerator
    constructor: ->
        @primes = []
        @candidate = 1
        @next = @_firstNext

    candidateIsPrime: ->
        for prime in @primes
            if prime.divides(@candidate)
                return false
        true

    _next: ->
        loop
            @candidate += 2
            if @candidateIsPrime()
                @primes.push(@candidate)
                return @candidate

    _firstNext: ->
        @next = @_next
        2


factor = (n) ->
    result = []
    primes = new PrimeGenerator()
    while n > 1
        prime = primes.next()
        if prime.divides(n)
            result.push(prime)
            n /= prime while n > 1 and prime.divides(n)
    result


console.log(factor(parseInt(process.argv[2])))
Das könnte man so fast 1:1 in Python umsetzen. Allerdings hat Python eine tolle Syntax um die Klasse durch eine Generatorfunktion zu ersetzen die fast wie die Rückruf-Variante von `primes()` aus der JavaScript-Lösung oben aussieht: die ``yield``-Anweisung.

Code: Alles auswählen

#!/usr/bin/env python
import sys


def iter_primes():
    primes = []
    candidate = 3
    yield 2
    while True:
        if all(candidate % prime != 0 for prime in primes):
            primes.append(candidate)
            yield candidate
        candidate += 2


def iter_factors(number):
    for prime in iter_primes():
        if number == 1:
            return
        if number % prime == 0:
            yield prime
            while number > 1 and number % prime == 0:
                number //= prime


def main():
    if len(sys.argv) < 1:
        print 'usage: {0} number'.format(sys.argv[0])
    else:
        try:
            number = int(sys.argv[1])
        except ValueError:
            print 'Not a valid number:', sys.argv[1]
        else:
            print list(iter_factors(number))


if __name__ == '__main__':
    main()

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 05:51
von snafu
Um's nochmal deutlich zu sagen: Man benötigt zum Finden von Primfaktoren (wie ich jetzt gelernt habe) definitiv keine `is_prime()`-Funktion. Das Prinzip ist einfach, dass man mit der kleinstmöglichen Zahl als ersten Kandidaten beginnt und dann aufsteigend vorgeht. Immer dann, wenn die zu zerlegende Zahl durch einen Kandidaten teilbar ist, dann kann man sie solange durch den Kandidaten teilen bis sie *nicht* mehr durch diesen Kandidaten teilbar ist. Damit hat man dann auch ausgeschlossen, dass die Zahl noch durch irgendwelche Vielfachen des Kandidaten teilbar ist. Beispiel: Wenn man eine Zahl fortwährend durch 2 teilt, dann wird sie definitiv nicht mehr durch 4, 6, 8, ... teilbar sein. Wichtig dabei ist halt, dass die Ursprungszahl auch tatsächlich geschrumpft wird. Wenn die geschrumpfte Zahl irgendwann nicht mehr glatt durch den Kandidaten teilbar ist, dann probiert man das weitere Schrumpfen der Zahl mit dem nächsten Kandidaten solange bis dieser nicht mehr geht, usw. Man fährt solange fort bis man zu einem Kandidaten kommt, dessen Wert größer ist als der Wert der fortwährend geschrumpften Zahl. Dann ist man fertig und der letzte Kandidat, der noch ein glatter Teiler war (also: derjenige, der *vor* dem zur Abbruchbedingung führenden Kandidaten dran kam), ist der gesuchte größte Primfaktor.

Durch den Vorgang des fortwährenden Schrumpfens spart man sich die quadratische Laufzeit, die man beim Einbau von `is_prime()` hätte, da wie gesagt ein Vielfaches einer Primzahl sofort "sehen" würde, dass es nicht glatt durch die geschrumpfte Zahl teilbar ist. Dies heißt dann automatisch, dass dieses Vielfache keine Primzahl sein kann. Wenn man das einmal verstanden hat, dann kann man sich an entsprechende Optimierungen machen, wie z.B. dem Testen von ausschließlich ungeraden Zahlen (außer der 2 natürlich) oder der Verwendung der Quadratwurzel, um das fortwährende Teilen abkürzen zu können. Ob man dann noch weitere Optimierungen einführen möchte, ist wohl Geschmackssache. Das, was Sirius3 zum Schluss gepostet hatte, ist zwar ganz nett und ich ziehe den Hut vor solchen Kenntnissen, aber wenn man es mit so großen Eingabewerten zu tun bekommt, dass ein solcher doch eher unschöner Algorithmus nötig wird, dann würde ich persönlich eher zu C greifen und den Algo dort implementieren - denn wenn's schon hochoptimiert sein soll, dann richtig. ;)

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 06:57
von Sirius3
@snafu: durch meine Handoptimierung der Assemblerausgabe einer C-Funktion konnte ich die Anzahl der Befehle nochmals halbieren. Für primitive Algorithmen kann es also durchaus den Aufwand lohnen, in die Tiefen der Hardware hinabzusteigen, obwohl noch nicht daraufhin geprüft habe, ob durch falsches Aligning noch ein paar Taktzyklen verloren gehen.

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 08:30
von snafu
@Sirius3: Du kannst ja gerne ein Erweiterungsmodul für Python in C schreiben, welches Inline-Assembler-Befehle benutzt, wenn du Spaß dran hast. Gibt es aber vermutlich auch schon in irgendeiner Form. ^^

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 18:37
von Gwunderi
snafu hat geschrieben:Um's nochmal deutlich zu sagen: Man benötigt zum Finden von Primfaktoren (wie ich jetzt gelernt habe) definitiv keine `is_prime()`-Funktion. Das Prinzip ist einfach, dass man mit der kleinstmöglichen Zahl als ersten Kandidaten beginnt und dann aufsteigend vorgeht. Immer dann, wenn die zu zerlegende Zahl durch einen Kandidaten teilbar ist, dann kann man sie solange durch den Kandidaten teilen bis sie *nicht* mehr durch diesen Kandidaten teilbar ist. Damit hat man dann auch ausgeschlossen, dass die Zahl noch durch irgendwelche Vielfachen des Kandidaten teilbar ist. Beispiel: Wenn man eine Zahl fortwährend durch 2 teilt, dann wird sie definitiv nicht mehr durch 4, 6, 8, ... teilbar sein. Wichtig dabei ist halt, dass die Ursprungszahl auch tatsächlich geschrumpft wird. Wenn die geschrumpfte Zahl irgendwann nicht mehr glatt durch den Kandidaten teilbar ist, dann probiert man das weitere Schrumpfen der Zahl mit dem nächsten Kandidaten solange bis dieser nicht mehr geht, usw. Man fährt solange fort bis man zu einem Kandidaten kommt, dessen Wert größer ist als der Wert der fortwährend geschrumpften Zahl. Dann ist man fertig und der letzte Kandidat, der noch ein glatter Teiler war (also: derjenige, der *vor* dem zur Abbruchbedingung führenden Kandidaten dran kam), ist der gesuchte größte Primfaktor.
Genau so isses (habe auch ich jetzt beim Pröbeln richtig begriffen).

Praktisch ist es aber nicht immer so einfach? Was ist z.B., wenn der erste Kandidat die Zahl selber ist, also n eine Primzahl ist? oder sie nur zwei Primfaktoren, z.B. 2 und eine Riesenzahl, hat? Hatte beim Pröbeln mit dem Programm von Sirius eine solche Primzahl gefunden, ich glaube eine 12-stellige (oder war sie noch höher?).

Verstehe das Programm von Sirius (noch) nicht, hatte in der Zwischenzeit auch keine Zeit, aber sie hat diese Primzahl im Nu herausgegeben. Möglich, dass es für noch sehr viel höhere Zahlen nicht mehr viel taugt : ) ... Überhaupt verstehe ich die meisten Programme hier nicht, es fehlt mir einfach noch das Grundwissen.

So wie ich mich kenne, werde ich mich aber morgen wieder ans Pröbeln machen ... oder soll ich es vorläufig belassen?
BlackJack hat geschrieben:Dein Hauptproblem, abgesehen von den vielen Sonderfällen die da unnötigerweise unterschieden werden, ist dass Du `n` nicht verkleinerst wenn Du einen Faktor gefunden hast. Dadurch testest Du deutlich mehr Teiler als nötig.

Und Du erzeugst dadurch auch mehr Primzahlen als nötig. Das kann man auf das Nötigste begrenzen in dem man das Programm so schreibt, dass jede gefundene Primzahl sofort nach dem sie gefunden wurde, verwendet wird um `n` zu verkleinern.
Ja, siehe oben. Die vielen Sonderfälle: ja, man kann das Programm bestimmt noch verkürzen, und all die Zwischenresultate hatte ich eigentlich für mich eingebaut, gehören nicht in ein fertiges Programm ... : ) Bin auch noch nicht dazu gekommen, Dein obiges Python-Programm zu testen, geschweige denn, es zu verstehen versuchen ...

Grüsslein, Gwunderi

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 19:33
von BlackJack
Gwunderi hat geschrieben:Praktisch ist es aber nicht immer so einfach?
Doch, praktisch ist das immer so einfach.
Was ist z.B., wenn der erste Kandidat die Zahl selber ist, also n eine Primzahl ist?
Dann wird so lange versucht durch Kandidaten zu teilen bis die Kandidaten grösser oder gleich der Wurzel aus `n` werden. Wenn bis dahin kein Teiler gefunden wurde ist `n` prim. Und wird zurückgegeben.
oder sie nur zwei Primfaktoren, z.B. 2 und eine Riesenzahl, hat?
Dann wird gleich beim ersten Kandidaten, also der 2, `n` solange durch zwei geteilt bis 2 kein Teiler mehr st. Übrig bleibt dann die ”Riesenzahl” die ja prim ist. Was damit passiert steht in der Antwort zur letzten Frage. Keine Magie. :-)

Re: Was ist falsch an meinem code?

Verfasst: Freitag 12. Juli 2013, 23:42
von Gwunderi
BlackJack hat geschrieben:Doch, praktisch ist das immer so einfach.
Hallo BlackJack

Was ist denn mit "Kandidat" gemeint? Ich nehme an, die Primzahlen?
Aber zumindest mit meinem Programm geht es gute 10 Sek. um nur die grösste Primzahl unter 100'000 zu finden. Und wenn die einzigen Primfaktoren von n = 2 und eine Riesenzahl sind, dann muss doch das Programm erst eben die Kandidaten bis n/2 finden?
Also entweder benutze ich das falsche Programm, um Primzahlen zu finden, oder dann verstehe ich etwas grundsätzlich nicht ... ?

Oder dann benutzt Du doch Magie?

Bis morgen bei klarerem Denkvermögen
Gwunderi

Re: Was ist falsch an meinem code?

Verfasst: Samstag 13. Juli 2013, 00:16
von Gwunderi
Fällt mir ein:
Müssen ja gar keine Primzahlen sein?

Beginnen wir mit:
n/2 geht nicht (mit "geht nicht" meine ich, dass 2 kein ganzzahliger Teiler von n ist)
n/3 geht nicht
n/5 geht nicht
n/7 geht

n2 = n/7

n2 / 7 geht

n3 = n2/7 = n/49
n3 / 7 geht nicht
n3 / 9 geht nicht

usw.

Könnte es so gemeint sein? Ginge das schnell genug? Wie gesagt, es ist schon etwas spät und ich schalte jetzt mal definitif (bis auf weiteres) ab ...

Re: Was ist falsch an meinem code?

Verfasst: Samstag 13. Juli 2013, 00:32
von BlackJack
@Gwunderi: Genau so ist es gemeint. Und genau das macht das letzte Programm von Sirius3 bevor er mit den Optimierungen angefangen hat ja auch.

Re: Was ist falsch an meinem code?

Verfasst: Samstag 13. Juli 2013, 05:58
von snafu
Wobei ich mir bei der Wheel Factorization gerade denke: Wenn man das ein bißchen aufhübscht bzw abstrahiert (z.B. durch Verwendung eines Generators zum Erzeugen des nächsten Kandidaten), dann sieht der Code auch in Python noch einigermaßen in Ordnung aus. Mit PyPy statt CPython kann man dann sowieso noch ordentlich Laufzeit abbauen. Also so gesehen kriegt man auch für recht große Primzahlen noch brauchbare Ausführungszeiten in Pythoncode hin, ohne dass man gleich zu C oder Assembler greifen müsste.
Gwunderi hat geschrieben:Was ist z.B., wenn der erste Kandidat die Zahl selber ist, also n eine Primzahl ist?
Dann hat man Pech und befindet sich im sogenannten Worst Case. Für solche Fälle versucht man, die Auswahl der auszuprobierenden Teiler (=Kandidaten) möglichst gering zu halten, sodass man Schleifendurchläufe einsparen kann. Du musst im Prinzip überlegen: "Wie kann ich mit möglichst wenig Schleifendurchläufen (vor allem bezogen auf den Worst Case) ein Ergebnis ermitteln, das garantiert für jede beliebige zu testende Zahl korrekt ist?"

Re: Was ist falsch an meinem code?

Verfasst: Samstag 13. Juli 2013, 13:15
von Gwunderi
BlackJack hat geschrieben:Genau so ist es gemeint. Und genau das macht das letzte Programm von Sirius3 bevor er mit den Optimierungen angefangen hat ja auch.
Öch, hatte ich gar nicht gemerkt ... :cry:
snafu hat geschrieben:Du musst im Prinzip überlegen: "Wie kann ich mit möglichst wenig Schleifendurchläufen (vor allem bezogen auf den Worst Case) ein Ergebnis ermitteln, das garantiert für jede beliebige zu testende Zahl korrekt ist?"
Und nichts einfacher als das :roll: [Ironie aus]