Nicht nachvollziehbares Verhalten von itertools.combinations

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.
BlackJack

@heiliga horsd: 14M Objektzeiger sind bei einem 32-Bit System auch 53 MiB. Aber ich mache mir da weniger Gedanken um den Speicher als um die Laufzeit diese grossen Listen zu erzeugen und zu serialisieren, zwischen den Prozessen zu übertragen, und wieder zu deserialisieren.

Wo so ein Generator aufhören muss, kann man ja von aussen zum Beispiel mit `itertools.islice()` bestimmen. Die interessante Frage ist eher der Algoritmus die x. lexikografische Kombination direkt aus einem gegebenen x zu erzeugen kann, damit man diesen Startpunkt hat. Das geht auf jeden Fall, ich kann mich da dunkel an eine Vorlesung zu dem Thema erinnern. Muss mal in meinen Unterlagen suchen.

Bei 4 Sekunden mit PyPy lohnt sich der Aufwand das zu parallelisieren ja eigentlich kaum noch. :-)

Ich habe hier einen Athlon64 X2 (Dual Core, 2,7 Ghz) allerdings mit 32-Bit-Betriebssystem. Da dauert ein sehr ähnliches Programm 11.635s.

Meinen zweiten Ansatz — sich die Kombinationen im ersten Durchlauf zu den entsprechenden Summen zu merken um nur noch die im zweiten Durchlauf zu testen wo Summe mal Anzahl gross genug ist — habe ich auch mal in C probiert. Das hat zwar nicht so eine katastrophale Laufzeit wie der Python-Versuch, ist aber trotzdem ≈5× langsamer als der „brute force” Ansatz bei dem einfach zweimal alle Kombinationen generiert werden. Ich dachte ja es lag bei Python an den dynamisch wachsenden Listen und dass ich das Problem in C umgehen kann, in dem ich einmal ein entsprechend grosses Array mit allen Kombinationen + Listenzeiger erzeuge und im Histogram einen Zeiger auf eine Kombination zu jeder Summe speichere und die Kombinationen dann über ihre Zeiger eine verkettete Liste bilden. Das ist Konstanter Aufwand pro Kombination. Das bisschen verketten ist aber anscheinend deutlich teurer als einfach die Kombinationen noch einmal zu generieren.

lotto_02.c:

Code: Alles auswählen

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define N       49
#define K       6
#define MAX_SUM (279 + 1)       /* = N + N-1 + N-2 + N-3 + N-4 + N-5 + 1*/

typedef struct Combination {
    uint8_t numbers[K];
    struct Combination *next;
} Combination;

typedef struct {
    uint32_t length;
    Combination *items;
} Combinations;

typedef struct {
    uint32_t count;
    Combination *head;
} HistogramEntry;

HistogramEntry histogram[MAX_SUM];
Combinations combinations;

uint32_t number_of_combinations(uint8_t n, uint8_t k)
{
    uint32_t result = 1;
    uint8_t i, max;
    
    max = (k < n - k) ? k : n - k;
    for (i = 0; i < max; ++i) {
        result = (result * (n - i)) / (i + 1);
    }
    
    return result;
}

void Combination_init(Combination *combination)
{
    uint8_t i;
    for (i = 0; i < K; ++i) {
        combination->numbers[i] = i + 1;
    }
    combination->next = NULL;
}

_Bool Combination_next(Combination *combination)
{
    int8_t i;
    uint8_t n;
    
    for (i = K - 1, n = N - 1; i >= 0 && combination->numbers[i] > n; --i, --n);
    if (i < 0) return false;
    ++combination->numbers[i];
    for (i = ++i; i < K; ++i) {
        combination->numbers[i] = combination->numbers[i - 1] + 1;
    }
    return true;
}

uint16_t Combination_sum(Combination *combination)
{
    uint8_t i;
    uint16_t result = 0;
    
    for (i = 0; i < K; ++i) {
        result += combination->numbers[i];
    }
    
    return result;
}

uint32_t Combination_product(Combination *combination)
{
    uint8_t i;
    uint32_t result = 1;
    
    for (i = 0; i < K; ++i) {
        result *= combination->numbers[i];
    }
    
    return result;
}

void Combination_print(Combination *combination)
{
    uint8_t i;
    
    for (i = 0; i < K; ++i) {
        printf("%2d ", combination->numbers[i]);
    }
    putchar('\n');
}

void Combinations_init(void)
{
    uint32_t i;
    Combination combination;
    
    combinations.length = number_of_combinations(N, K);
    combinations.items = malloc(
        combinations.length * sizeof(*combinations.items)
    );
    i = 0;
    Combination_init(&combination);
    do {
        memcpy(&combinations.items[i++], &combination, sizeof(combination));
    } while(Combination_next(&combination));
    assert(i == combinations.length);
}

void Combinations_destroy(void)
{
    combinations.length = 0;
    free(combinations.items);
}

void calculate_histogram()
{
    Combination *combination;
    HistogramEntry *entry;
    uint32_t i;
    
    memset(histogram, 0, sizeof(histogram));
    
    for (
        i = 0, combination = combinations.items;
        i < combinations.length;
        ++i, ++combination
    ) {
        entry = &histogram[Combination_sum(combination)];
        ++entry->count;
        combination->next = entry->head;
        entry->head = combination;
    }
}

void search_combination(void)
{
    Combination *combination;
    uint16_t i;
    uint32_t x;
    
    for (i = 0; i < MAX_SUM; ++i) {
        x = i * histogram[i].count;
        if (x > 2000000) {
            combination = histogram[i].head;
            while (combination) {
                if (x == Combination_product(combination)) {
                    Combination_print(combination);
                }
                combination = combination->next;
            }
        }
    }
}

int main(void)
{
    Combinations_init();
    calculate_histogram();
    search_combination();
    Combinations_destroy();
    return 0;
}
Damit sieht der Stand meiner Versuche so aus:

Code: Alles auswählen

Filename        Impl.   Runtime         Factor
==============  ======  =========  ===========
lotto_02.py     python  6m41.216s        -7.24
lotto_01.py     python    34.144s         1.00
lotto_02.py     pypy      21.054s         1.62
lotto_01.py     pypy      11.635s         2.93
lotto_02.c                 1.316s        25.95  (Neu)
lotto_01.c                 0.267s       127.88
@snafu: Wenn da nicht der Forscherdrang wäre. :-) Bei meiner lahmen Kiste sind es immerhin noch ≈11s. Und auf dem C64 dauert das erste Aufzählen schon mehr als eine Stunde (da habe ich abgebrochen). Wäre schon schön wenn man das besser hinbekommen würde. :-)
heiliga horsd

http://www.matheforum.net/read?i=899025

http://de.wikipedia.org/wiki/Stirling-Z ... weiter_Art

Hab mich da noch nicht weiter eingelesen, sollte nur mal als Hinweis für alle dienen, dass es da anscheinend tatsächlich einen mathematischen Weg gibt.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Man sollte vielleicht überlegen, welche Teilannahmen man treffen kann. Zum Beispiel weiß ich, dass jede Kombination, die (1, 2, 3, 4, 5) enthält und durch eine beliebige übrige Zahl (also von 6-49) ergänzt wird, als Produkt niemals größer als 2 Mio sein kann. Das Maximal mögliche Ergebnis ist 5880 für 1*2*3*4*5*49, ähnliches gilt für 2*3*4*5*6*49. Ich weiß nicht, inwiefern das erweiterbar ist, ohne "logische Fehler" einzubauen. Falls er klappt, könnte man am Ende eine Formel daraus machen. Ist aber erstmal nur ein Gedanke...
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@heiliga horsd: Vielleicht am Rande nochmal zum Generator-Verhalten, sofern dir das bisher nicht ganz klar geworden ist: So ein Generator-Objekt ist ja im Grunde nicht viel mehr als eine Zustandmaschine, die auf Anforderung den jeweils nächsten Zustand berechnet und diesen zurückgibt. Es werden in deinem Anwendungsfall also nicht irgendwo (meinetwegen in einem C-Array) alle Kombinationen gespeichert und nur einzeln ausgespuckt, sondern die nächste Kombination steht wirklich erst dann fest, wenn sie "an der Reihe" ist. Und der Generator merkt sich zuvor zurückgelieferte Kombinationen auch nicht.

Durch dieses Verhalten können Generatoren potenziell Milliarden von Elementen zurückgeben (sogar unendlich viele), ohne dass es irgendwelche negativen Effekte auf den Speicherverbrauch hat. Das Problem fängt erst dann an, wenn man selber die Ausgaben z.B. in einer Liste sammelt. Generatoren sind also grundsätzlich eher dazu gedacht, dass ihre zurückgelieferten Elemente sofort verarbeitet werden und nicht erst in irgendeinem Container abgelegt werden.

Wie du dir jetzt vielleicht schon denken kannst, bringt es durch diesen Umstand nicht sondernlich viel, wenn Generatoren am Ende wieder von selbst auf den Ausgangszustand zurückspringen würden. Das hätte letztlich den selben Effekt wie das Erstellen eines neuen Generators, da jegliche Berechnung wieder von vorn beginnen müsste und sich auch wieder Schritt für Schritt weiter "tasten" müsste. Das ist ja auch der Grund, wieso man nicht nach Belieben an eine spätere Stelle im Generator springen kann, um möglicherweise tausende von vorherigen Elementen zu überspringen. Es geht technisch einfach nicht. Was natürlich ginge, wäre eine Option, die den Generator erst für eine spätere Stelle im Ablauf initialsiert, sodass er von dort an weitermachen kann. Dies bietet `itertools.combinations()` einem aber leider nicht an.
BlackJack

Ich habe jetzt `combinations_init()` und `combinations_next()` aus meinem C-Programm in einer Generator-Funktion in Python zusammengefasst. Und zusätzlich die Idee aus dem C-Programm übernommen das Histogramm in einer Liste und nicht in einem Wörterbuch zu speichern. Letzteres hat einen geringen Geschwindigkeitsvorteil weil direkt auf die Positionen zugegriffen werden kann, ohne das intern erst ein Hashwert benutzt werden muss.

Ausserdem kann die Generatorfunktion nicht nur mit der kleinsten lexikographischen Kombination beginnen, sondern an jeder beliebigen Position. Das verdanke ich dem Buch „Combinatorial Algorithms: Generation, Enumeration, and Search”, wo ich einen passenden Algorithmus gefunden habe (die `unrank()`-Funktion).

lotto_03.py:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from itertools import combinations
from operator import mul

N, K = 49, 6
MAX_SUM = sum(N - i for i in xrange(K))


def product(iterable):
    return reduce(mul, iterable, 1)


def number_of_combinations(n, k):
    if k > n or n < 0 or k < 0:
        return 0
    result = 1
    for i in xrange(min(k, n - k)):
        result = (result * (n - i)) / (i + 1)
    return result


def unrank(r, n=N, k=K):
    result = list()
    x = 1
    for i in xrange(1, k + 1):
        y = number_of_combinations(n - x, k - i)
        while y <= r:
            r -= y
            x += 1
            y = number_of_combinations(n - x, k - i)
        result.append(x)
        x += 1
    return result


def lotto_combinations(start=0, n=N, k=K):
    combination = unrank(start, n, k)
    while True:
        yield combination
        i = k - 1
        j = n - 1
        while i >= 0 and combination[i] > j:
            i -= 1
            j -= 1
        if i < 0:
            break
        combination[i] += 1
        for i in xrange(i + 1, k):
            combination[i] = combination[i - 1] + 1


def main():
    histogram = [0] * (MAX_SUM + 1)
    for combination in lotto_combinations():
        histogram[sum(combination)] += 1
    
    for combination in lotto_combinations():
        the_sum = sum(combination)
        x = the_sum * histogram[the_sum]
        if x >= 2000000 and product(combination) == x:
            print combination


if __name__ == '__main__':
    main()
Das ist in CPython ausgeführt jetzt erwartungsgemäss langsamer als mit dem in C implementierten `itertools.combinations()`. Aber nur um den Faktor ≈2 — ich hätte Schlimmeres erwartet. Besonders interessant finde ich aber, dass PyPy das genau so schnell, beziehungsweise sogar ein wenig schneller, ausführt als die erste Variante die noch `itertools.combinations()` verwendet hat! Da haben wir also einen Kombinationsgenerator in reinem Python der nicht langsamer ausgeführt wird als der in C geschriebene aus dem `itertools`-Modul. Cool.

Code: Alles auswählen

Filename        Impl.   Runtime         Factor
==============  ======  =========  ===========
lotto_02.py     python  6m41.216s        -7.24
lotto_03.py     python  1m10.051s        -2.05 (Neu)
lotto_01.py     python    34.144s         1.00
lotto_02.py     pypy      21.054s         1.62
lotto_01.py     pypy      11.635s         2.93
lotto_03.py     pypy      10.099s         3.38 (Neu)
lotto_02.c                 1.316s        25.95
lotto_01.c                 0.267s       127.88
Mit dem Quelltext hat man nun die Möglichkeit das Programm effizienter zu parallelisieren.
heiliga horsd

@snafu: Danke für die Generator-Erläuterung, mir hats schon ein bisschen gedämmert, jetzt hab ich das Schwarz auf Weiß :)
Wie man das mit den Produkten machen kann weiß ich so auch nicht. Frage mich auch gerade, ob es effizienter ist, paar Millionen Größenvergleiche anzustellen um sich das multiplizieren zu sparen oder eben gleich zu multiplizieren

@BlackJack: Ich hab das Gefühl du hast Gefallen an dem Problem gefunden :D
Da ich v.a. jetzt am WE Klausur- & Wetterbedingt wenig Zeit habe muss ich schauen wann ich zum testen und weiter probieren komme. Das mit dem unrank hört sich aber relativ cool an.

@all: Hat sich jemand das mit den Stirling-Zahlen angeschaut bzw. kennt sich damit aus? Bin da mit meinem 'begrenzten' Oberstufenwissen noch nicht auf dem mathematischen Level um da viel raus holen zu können... :K
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@BlackJack: Kann ich bestätigen. Der in Python implementierte Kombinationsgenerator ist hier ein paar Zehntel Sekunden schneller als `itertools.combinations()`. Auch die Verwendung der Liste anstelle des Wörterbuchs ist schneller. PyPy ist echt der Hammer.

//edit: Wobei ja auch der verwendete Algorithmus aus deinem Buch recht effizient zu sein scheint, wenn man sich mal den Performance-Schub allein in CPython anguckt.

//edit2: Achso, jetzt schnall ich das. Nur `unrank()` ist aus dem Buch. Den Rest hattest du ja schon vorher. Und `unrank()` kommt erst dann wirklich ins Spiel, wenn ein wahlweiser Zugriff benötigt wird, d.h. Verändern des `start`-Parameters für den Kombinationsgenerator. :)
Zuletzt geändert von snafu am Samstag 23. Juni 2012, 18:30, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

heiliga horsd hat geschrieben:@all: Hat sich jemand das mit den Stirling-Zahlen angeschaut bzw. kennt sich damit aus? Bin da mit meinem 'begrenzten' Oberstufenwissen noch nicht auf dem mathematischen Level um da viel raus holen zu können... :K
Um ehrlich zu sein, ist mir bisher nicht klar geworden, wie der Stirling-Ansatz da überhaupt sinnvoll zu beitragen kann. Soweit ich weiß, hilft das eher bei Wahrscheinlichkeitsberechnungen. Evtl hat dich der Antwortende auch einfach nicht richtig verstanden...
BlackJack

@snafu: Bei Lotto geht es doch auch um Wahrscheinlichkeiten. :-)

Bei Wahrscheinlichkeitsrechnung geht es oft um das Zählen von möglichen Ergebnissen mit bestimmten Eigenschaften. Wenn Du beantworten kannst wie gross die Wahrscheinlichkeit ist, dass bei einer Ziehung eine Kombination mit einer festen aber beliebigen Summe x kommt, dann weisst Du auch wie viele Kombinationen es für diese Summe gibt. Und umgekehrt.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

BlackJack hat geschrieben:Wenn Du beantworten kannst wie gross die Wahrscheinlichkeit ist, dass bei einer Ziehung eine Kombination mit einer festen aber beliebigen Summe x kommt, dann weisst Du auch wie viele Kombinationen es für diese Summe gibt. Und umgekehrt.
Nun ja, aber die konkrete Zahlenfolge finde ich damit ja trotzdem nicht heraus.

Übrigens hab ich die Idee, möglichst viele Zahlenfolgen vorab überspringen zu können, wieder aufgegriffen. Die Fee sagt ja: "[...] dann erhältst du eine sehr große Zahl von einigen Millionen, und diese Zahl kommt auch raus, wenn man alle sechs Lottozahlen miteinander malnimmt." Das heißt ja mit anderen Worten, alle Kombinationen, deren Produkt nicht mindestens 2 Mio ergibt, kommen gar nicht erst infrage.

So, jetzt verläuft die Erzeugung der Kombinationen ja nach einem bestimmten Muster. Man kann sich quasi einen Tacho denken, bei dem man mit dem Kilometerstand 123456 losfährt, wobei die Tachoeinheiten nicht von 0-9, sondern von 1-49 gehen. Wenn die am weitesten rechts stehende Einheit bei dem Maximalwert 49 angekommen ist, dann erhöht sich die links daneben stehende Einheit um eins. Im Gegensatz zu einer gewöhnlichen Tachoanzeige springt die rechte Einheit jedoch auf den Wert `linker_nachbar + 1`. Soll heißen: (1, 2, 3, 4, 5, 49) -> (1, 2, 3, 4, 6, 7) -> (1, 2, 3, 4, 6, 8) -> usw.

Da es dank `unrank()` nun wesentlich einfacher geworden ist, die Ergebnisse zu untersuchen, hab ich mal versucht, eine bestimmte "Tachozähler-Konstellation" zu bestimmen. Sieht noch etwas komisch aus, weil es keine Formel ist, aber vielleicht ein guter Anfang:

Code: Alles auswählen

>>>> unrank((49-6)+(49-6)+(48-6)+(47-6)+(46-6)+(45-6))
[1, 2, 3, 4, 10, 49]
>>>> unrank((49-6)+(49-6)+(48-6)+(47-6)+(46-6))
[1, 2, 3, 4, 9, 49]
>>>> unrank((49-6)+(49-6)+(48-6)+(47-6))
[1, 2, 3, 4, 8, 49]
Die zweite Tachoeinheit kann damit also mehr oder weniger gesteuert werden. Warum tu ich das? Nun, wenn sich die Tachoeinheiten jetzt nach und nach mit dem Maximalwert füllen würden, dann hat man ja beim Ermitteln des Produkts sozusagen auf der rechten Seite einen Faktor, welcher eine Potenz von 49 (bzw `N`) ist. Zur Veranschaulichung die Berechnung des Produkts:

Code: Alles auswählen

>>>> 1*2*3*4*5*49
5880
>>>> 1*2*3*4*49*49
57624
>>>> 1*2*3*49*49*49
705894
>>>> 1*2*49*49*49*49
11529602
Ich bleibe also bis 49^3 unter dem Minimalwert für das Produkt. Aufgrund des oben erläuterten Erzeugungsmusters ist auch garantiert, dass vorher kein "zu großes" Produkt herauskommen kann, womit man mindestens die bis zu dem Punkt theoretisch erzeugten Kombinationen unter den Tisch fallen lassen könnte, oder sehe ich das falsch?

Wie gesagt, da muss noch ein bißchen Form rein (ich will natürlich keinen endlos langen Term da stehen haben, sondern etwas hübsches mit Variablen) und so ganz zu Ende gedacht ist die Idee auch noch nicht. Aber an sich wage ich mal zu behaupten, dass man damit dem Ziel der reinen Berechnung des Ergebnisses schon ein Stück näher gekommen ist. Zumindest dürfte es schonmal die Zeit zum Durchprobieren merklich vermindern (also natürlich erst dann, wenn die Formel zum Erhalten des ersten Kandidaten komplett definiert ist).

//edit: Ups. Die 49 geht natürlich nur einmal pro Kombination. But you get the idea - perhaps. :D
BlackJack

Vorerst wohl die letzte Variante von mir — jetzt parallelisiert.
lotto_04.py:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from functools import partial
from itertools import combinations, islice
from multiprocessing import cpu_count, Pool
from operator import add, mul

N, K = 49, 6
MAX_SUM = sum(N - i for i in xrange(K))


def product(iterable):
    return reduce(mul, iterable, 1)


def number_of_combinations(n, k):
    if k > n or n < 0 or k < 0:
        return 0
    result = 1
    for i in xrange(min(k, n - k)):
        result = (result * (n - i)) / (i + 1)
    return result


def unrank(r, n, k):
    result = list()
    x = 1
    for i in xrange(1, k + 1):
        y = number_of_combinations(n - x, k - i)
        while y <= r:
            r -= y
            x += 1
            y = number_of_combinations(n - x, k - i)
        result.append(x)
        x += 1
    return result


def lotto_combinations(start=0, n=N, k=K):
    combination = unrank(start, n, k)
    while True:
        yield combination
        i = k - 1
        j = n - 1
        while i >= 0 and combination[i] > j:
            i -= 1
            j -= 1
        if i < 0:
            break
        combination[i] += 1
        for i in xrange(i + 1, k):
            combination[i] = combination[i - 1] + 1


def count_sums(length, start):
    histogram = [0] * (MAX_SUM + 1)
    for combination in islice(lotto_combinations(start), length):
        histogram[sum(combination)] += 1
    return histogram


def search_combination(sum_histogram, length, start):
    result = list()
    for combination in islice(lotto_combinations(start), length):
        the_sum = sum(combination)
        x = the_sum * sum_histogram[the_sum]
        if x >= 2000000 and product(combination) == x:
            result.append(tuple(combination))
    return result


def main():
    combinatio_count = number_of_combinations(N, K)
    chunksize = (combinatio_count // cpu_count()) + 1
    offsets = range(0, combinatio_count, chunksize)
    
    pool = Pool()
    
    sum_histogram = [0] * (MAX_SUM + 1)
    for histogram in pool.map(partial(count_sums, chunksize), offsets, 1):
        sum_histogram = map(add, sum_histogram, histogram)
    
    for combinations in pool.map(
        partial(search_combination, sum_histogram, chunksize), offsets, 1
    ):
        for combination in combinations:
            print combination


if __name__ == '__main__':
    main()
Ist damit die schnellste Python-Version von mir, wenn man es mit PyPy ausführt. Noch einmal ≈19% schneller als die bisher schnellste sequentielle Variante mit PyPy.

Code: Alles auswählen

Filename        Impl.   Runtime         Factor
==============  ======  =========  ===========
lotto_02.py     python  6m41.216s        -7.24
lotto_03.py     python  1m10.051s        -2.05
lotto_04.py     python    39.093s        -1.14 (Neu)
lotto_01.py     python    34.144s         1.00
lotto_02.py     pypy      21.054s         1.62
lotto_01.py     pypy      11.635s         2.93
lotto_03.py     pypy      10.099s         3.38
lotto_04.py     pypy       8.219s         4.15 (Neu)
lotto_02.c                 1.316s        25.95
lotto_01.c                 0.267s       127.88
heiliga horsd

Hallo,

entschuldigung für die späte Antwort, habe grade wenig Zeit...

Hab die Lösung mal durch PyPy gejagt, ist jetzt nach 2 Sekunden durch.... aber ich schätze mal wirklich noch schneller schafft mans mit Python wohl wirklich nicht.

Gruß heiliga horsd
heiliga horsd

So, hab mir jetzt den Code nochmal angeschaut und mir die Messergebnisse mal durch den Kopf gehen lassen. Ich frage mich gerade, warum Lotto04.py (Parallelisiert) mit CPython langsamer ist als Lotto01.py (Sequentiell), mit PyPy der Sachverhalt jedoch genau anders herum ist?
BlackJack

@heiliga horsd: Weil in der 4er Variante der Kombinationsgenerator in Python geschieben ist und das ist selbst parallelisiert langsamer als `itertools.combinations()` was in CPython in C implementiert ist. Bei PyPy macht es keinen Unterschied ob man `itertools.combinations()` oder die in Python geschriebene Generatorfunktion verwendet. Das war bei mir die 3er Variante. Da ist PyPy bei der 1er und der 3er ungefähr gleich schnell, aber CPython bei der 3er *deutlich* langsamer.
BlackJack

Noch mal zwei Datenpunkte für einen Raspberry Pi (mit Raspbian als Betriebssystem): ``lotto_01.py`` hat 11 Minuten und 16 Sekunden gebraucht und ``lotto_01.c`` war in knapp unter 2s fertig.
Antworten