Python 2 mit pypy und pp: n = 15 in 3 Minuten
Auch nur eine einfache rohe Kraft. Interessant zu sehen, dass ich mit C ++ fast die gleiche Geschwindigkeit wie kuroi neko bekomme. Mein Code kann erreichenn = 12
in ca. 5 Minuten erreicht werden. Und ich führe es nur auf einem virtuellen Kern aus.
Bearbeiten: Reduziert den Suchraum um den Faktor n
Mir ist aufgefallen, dass ein zyklisierter Vektor A*
von A
dieselben Zahlen wie Wahrscheinlichkeiten (dieselben Zahlen) wie der ursprüngliche Vektor erzeugt, A
wenn ich darüber iteriere B
. Eg Der Vektor (1, 1, 0, 1, 0, 0)
hat die gleichen Wahrscheinlichkeiten als jeder der Vektoren (1, 0, 1, 0, 0, 1)
, (0, 1, 0, 0, 1, 1)
, (1, 0, 0, 1, 1, 0)
, (0, 0, 1, 1, 0, 1)
und , (0, 1, 1, 0, 1, 0)
wenn eine zufällige Auswahl B
. Deshalb muss ich nicht jede dieser sechs Vektoren durchlaufen, aber nur etwa 1 und ersetzen count[i] += 1
mit count[i] += cycle_number
.
Dies reduziert die Komplexität von Theta(n) = 6^n
zu Theta(n) = 6^n / n
. Deshalb ist n = 13
es ungefähr 13 mal so schnell wie meine Vorgängerversion. Es rechnet n = 13
in ca. 2 Minuten 20 Sekunden. Dafür ist n = 14
es noch etwas zu langsam. Es dauert ungefähr 13 Minuten.
edit 2: Multi-Core-Programmierung
Mit der nächsten Verbesserung nicht wirklich zufrieden. Ich habe mich entschlossen, mein Programm auch auf mehreren Kernen auszuführen. Auf meinen 2 + 2 Kernen kann ich jetzt n = 14
in ca. 7 Minuten rechnen . Nur ein Faktor 2 Verbesserung.
Der Code ist in diesem Github-Repo verfügbar: Link . Die Mehrkernprogrammierung macht das etwas hässlich.
Bearbeiten 3: Reduzieren des Suchraums für A
Vektoren und B
Vektoren
Ich bemerkte die gleiche Spiegelsymmetrie für die Vektoren A
wie kuroi neko. Immer noch nicht sicher, warum das funktioniert (und ob es für jeden funktioniert)n
).
Die Reduzierung des Suchraums für B
Vektoren ist etwas cleverer. Ich habe die Erzeugung der Vektoren ( itertools.product
) durch eine eigene Funktion ersetzt. Grundsätzlich beginne ich mit einer leeren Liste und lege sie auf einen Stapel. Bis der Stapel leer ist, entferne ich eine Liste. Wenn sie nicht dieselbe Länge hat wie n
, erstelle ich 3 weitere Listen (indem ich -1, 0, 1 anhänge) und schiebe sie auf den Stapel. Wenn eine Liste die gleiche Länge hat wie n
, kann ich die Summen auswerten.
Jetzt, da ich die Vektoren selbst generiere, kann ich sie filtern, je nachdem, ob ich die Summe = 0 erreichen kann oder nicht. Wenn z. B. mein Vektor A
ist (1, 1, 1, 0, 0)
und mein Vektor B
aussieht (1, 1, ?, ?, ?)
, weiß ich, dass ich die nicht ?
mit Werten füllen kann , so dass A*B = 0
. Ich muss also nicht alle 6 Vektoren B
des Formulars durchlaufen (1, 1, ?, ?, ?)
.
Wir können dies verbessern, wenn wir die Werte für 1 ignorieren. Wie in der Frage erwähnt, sind die Werte für i = 1
die Sequenz A081671 . Es gibt viele Möglichkeiten, diese zu berechnen. Ich wähle die einfache Wiederholung: a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
. Da wir i = 1
im Grunde genommen in kürzester Zeit rechnen können, können wir weitere Vektoren nach filtern B
. ZB A = (0, 1, 0, 1, 1)
und B = (1, -1, ?, ?, ?)
. Wir können Vektoren ignorieren, bei denen der erste ? = 1
, weil der A * cycled(B) > 0
, für alle diese Vektoren gilt. Ich hoffe du kannst folgen. Es ist wahrscheinlich nicht das beste Beispiel.
Damit kann ich n = 15
in 6 Minuten rechnen .
bearbeiten 4:
Schnell umgesetzt kuroi Nekos große Idee, die besagt, dass B
und -B
erzeugt die gleichen Ergebnisse. Beschleunigung x2. Die Implementierung ist jedoch nur ein kurzer Hack. n = 15
in 3 minuten.
Code:
Den vollständigen Code finden Sie unter Github . Der folgende Code ist nur eine Darstellung der Hauptfunktionen. Ich habe Importe weggelassen, Multicore-Programmierung, Drucken der Ergebnisse, ...
count = [0] * n
count[0] = oeis_A081671(n)
#generating all important vector A
visited = set(); todo = dict()
for A in product((0, 1), repeat=n):
if A not in visited:
# generate all vectors, which have the same probability
# mirrored and cycled vectors
same_probability_set = set()
for i in range(n):
tmp = [A[(i+j) % n] for j in range(n)]
same_probability_set.add(tuple(tmp))
same_probability_set.add(tuple(tmp[::-1]))
visited.update(same_probability_set)
todo[A] = len(same_probability_set)
# for each vector A, create all possible vectors B
stack = []
for A, cycled_count in dict_A.iteritems():
ones = [sum(A[i:]) for i in range(n)] + [0]
# + [0], so that later ones[n] doesn't throw a exception
stack.append(([0] * n, 0, 0, 0, False))
while stack:
B, index, sum1, sum2, used_negative = stack.pop()
if index < n:
# fill vector B[index] in all possible ways,
# so that it's still possible to reach 0.
if used_negative:
for v in (-1, 0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, True))
else:
for v in (0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, v == 1))
else:
# B is complete, calculate the sums
count[1] += cycled_count # we know that the sum = 0 for i = 1
for i in range(2, n):
sum_prod = 0
for j in range(n-i):
sum_prod += A[j] * B[i+j]
for j in range(i):
sum_prod += A[n-i+j] * B[j]
if sum_prod:
break
else:
if used_negative:
count[i] += 2*cycled_count
else:
count[i] += cycled_count
Verwendung:
Du musst pypy installieren (für Python 2 !!!). Das parallele Python-Modul ist nicht für Python 3 portiert. Anschließend müssen Sie das parallele Python-Modul pp-1.6.4.zip installieren . Extrahieren Sie es cd
in den Ordner und rufen Sie pypy setup.py install
.
Dann kannst du mein Programm mit aufrufen
pypy you-do-the-math.py 15
Es wird automatisch die Anzahl der CPUs ermittelt. Nach dem Beenden des Programms kann es zu Fehlermeldungen kommen. Ignorieren Sie diese einfach. n = 16
sollte auf Ihrem Rechner möglich sein.
Ausgabe:
Calculation for n = 15 took 2:50 minutes
1 83940771168 / 470184984576 17.85%
2 17379109692 / 470184984576 3.70%
3 3805906050 / 470184984576 0.81%
4 887959110 / 470184984576 0.19%
5 223260870 / 470184984576 0.05%
6 67664580 / 470184984576 0.01%
7 30019950 / 470184984576 0.01%
8 20720730 / 470184984576 0.00%
9 18352740 / 470184984576 0.00%
10 17730480 / 470184984576 0.00%
11 17566920 / 470184984576 0.00%
12 17521470 / 470184984576 0.00%
13 17510280 / 470184984576 0.00%
14 17507100 / 470184984576 0.00%
15 17506680 / 470184984576 0.00%
Notizen und Ideen:
- Ich habe einen i7-4600m Prozessor mit 2 Kernen und 4 Threads. Es spielt keine Rolle, ob ich 2 oder 4 Threads verwende. Die CPU-Auslastung beträgt 50% bei 2 Threads und 100% bei 4 Threads, dauert aber immer noch genauso lange. Ich weiß nicht warum. Ich habe überprüft, dass jeder Thread nur die Hälfte der Datenmenge hat, wenn es 4 Threads gibt, die Ergebnisse überprüft, ...
- Ich benutze viele Listen. Python ist nicht sehr effizient im Speichern, ich muss viele Listen kopieren, ... Also habe ich mir überlegt, stattdessen eine Ganzzahl zu verwenden. Ich könnte die Bits 00 (für 0) und 11 (für 1) im Vektor A und die Bits 10 (für -1), 00 (für 0) und 01 (für 1) im Vektor B verwenden von A und B müsste ich nur die
A & B
Blöcke 01 und 10 berechnen und zählen. Radfahren kann durch Verschieben des Vektors und Verwenden von Masken erfolgen. Ich habe das alles tatsächlich implementiert. Sie finden es in einigen meiner älteren Commits auf Github. Es stellte sich aber heraus, langsamer zu sein als mit Listen. Ich denke, pypy optimiert die Listenoperationen wirklich.