Die Herausforderung besteht darin, den schnellstmöglichen Code für die Berechnung der Permanenz einer Matrix zu schreiben .
Die Permanenz einer n
-by- n
Matrix A
= ( a
i,j
) ist definiert als
Hier wird S_n
die Menge aller Permutationen von dargestellt [1, n]
.
Als Beispiel (aus dem Wiki):
In dieser Frage sind Matrizen alle quadratisch und haben nur die Werte -1
und 1
in ihnen.
Beispiele
Eingang:
[[ 1 -1 -1 1]
[-1 -1 -1 1]
[-1 1 -1 1]
[ 1 -1 -1 1]]
Permanent:
-4
Eingang:
[[-1 -1 -1 -1]
[-1 1 -1 -1]
[ 1 -1 -1 -1]
[ 1 -1 1 -1]]
Permanent:
0
Eingang:
[[ 1 -1 1 -1 -1 -1 -1 -1]
[-1 -1 1 1 -1 1 1 -1]
[ 1 -1 -1 -1 -1 1 1 1]
[-1 -1 -1 1 -1 1 1 1]
[ 1 -1 -1 1 1 1 1 -1]
[-1 1 -1 1 -1 1 1 -1]
[ 1 -1 1 -1 1 -1 1 -1]
[-1 -1 1 -1 1 1 1 1]]
Permanent:
192
Eingang:
[[1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1],
[1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1],
[-1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1],
[-1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1],
[-1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1],
[1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1],
[1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1],
[1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
[1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1],
[-1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1],
[-1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1],
[1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1],
[-1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1],
[1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1],
[1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
[1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
[-1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1],
[1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1],
[1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1],
[-1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1]]
Permanent:
1021509632
Die Aufgabe
Sie sollten Code schreiben, der bei einer n
durch n
Matrix seine bleibende Zahl ausgibt.
Da ich Ihren Code testen muss, wäre es hilfreich, wenn Sie mir eine einfache Möglichkeit geben könnten, eine Matrix als Eingabe für Ihren Code zu verwenden, z. B. durch Einlesen von standard in.
Seien Sie gewarnt, dass die bleibende Zahl groß sein kann (die All-1s-Matrix ist der Extremfall).
Partituren und Krawatten
Ich werde Ihren Code auf zufälligen + -1 Matrizen mit zunehmender Größe testen und stoppen, wenn Ihr Code zum ersten Mal länger als 1 Minute auf meinem Computer dauert. Die Bewertungsmatrizen werden für alle Einreichungen konsistent sein, um die Fairness zu gewährleisten.
Wenn zwei Personen die gleiche Punktzahl erzielen, ist der Gewinner derjenige, der für diesen Wert von am schnellsten ist n
. Wenn diese innerhalb einer Sekunde voneinander entfernt sind, ist dies die zuerst veröffentlichte.
Sprachen und Bibliotheken
Sie können jede verfügbare Sprache und Bibliothek verwenden, die Sie möchten, aber keine bereits vorhandene Funktion, um die permanente zu berechnen. Wo immer möglich, wäre es gut, wenn Sie Ihren Code ausführen könnten. Fügen Sie daher bitte eine vollständige Erklärung dazu bei, wie Sie Ihren Code unter Linux ausführen / kompilieren, wenn dies überhaupt möglich ist. "
Referenzimplementierungen
Es gibt bereits eine Codegolf-Frage mit viel Code in verschiedenen Sprachen zur Berechnung der Permanenz für kleine Matrizen. Mathematica und Maple haben auch permanente Implementierungen, wenn Sie auf diese zugreifen können.
Mein Computer Die Timings werden auf meinem 64-Bit-Computer ausgeführt. Dies ist eine Ubuntu-Standardinstallation mit 8 GB RAM, AMD FX-8350 Eight-Core-Prozessor und Radeon HD 4250. Dies bedeutet auch, dass ich in der Lage sein muss, Ihren Code auszuführen.
Niedrige Informationen zu meiner Maschine
cat /proc/cpuinfo/|grep flags
gibt
Fahnen: FPU vme de pse tsc msr PAE mce CX8 APIC September mtrr PGE mca cmov pat PSE36 CLFLUSH MMX fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm CONSTANT_TSC rep_good NOPlat NONSTOP_TSC extd_apicid aperfmperf pni pclmulqdq überwachen SSSE3 fma CX16 sse4_1 sse4_2 popcnt aes XSAVE AVX f16c lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch
Ich werde eine eng verwandte mehrsprachige Folgefrage stellen, die nicht unter dem großen Int-Problem leidet, damit Liebhaber von Scala , Nim , Julia , Rust und Bash auch ihre Sprachen zur Schau stellen können.
Bestenliste
- n = 33 (45 Sekunden. 64 Sekunden für n = 34). Ton Hospel in C ++ mit g ++ 5.4.0.
- n = 32 (32 Sekunden). Dennis in C mit gcc 5.4.0 unter Verwendung der gcc-Flags von Ton Hospel.
- n = 31 (54 Sekunden). Christian Sievers in Haskell
- n = 31 (60 Sekunden). primo in rpython
- n = 30 (26 Sekunden). Ezrast in Rust
- n = 28 (49 Sekunden). xnor mit Python + pypy 5.4.1
- n = 22 (25 Sekunden). Shebang mit Python + Pypy 5.4.1
Hinweis . In der Praxis variieren die Timings für Dennis und Ton Hospel aus mysteriösen Gründen stark. Zum Beispiel scheinen sie schneller zu sein, nachdem ich einen Webbrowser geladen habe! Die angegebenen Zeiten sind die schnellsten in allen Tests, die ich gemacht habe.
quelle
Antworten:
gcc C ++ n ≈ 36 (57 Sekunden auf meinem System)
Verwendet die Glynn-Formel mit einem Gray-Code für Aktualisierungen, wenn alle Spaltensummen gerade sind, andernfalls wird die Ryser-Methode verwendet. Threaded und vektorisiert. Optimiert für AVX, erwarten Sie also nicht viel von älteren Prozessoren. Sorgen Sie nicht
n>=35
für eine Matrix mit nur +1, auch wenn Ihr System schnell genug ist, da der signierte 128-Bit-Akku überläuft. Bei Zufallsmatrizen werden Sie wahrscheinlich nicht auf den Überlauf stoßen. Fürn>=37
die internen Multiplikatoren beginnt ein Überlauf für alle1/-1
Matrizen. Verwenden Sie dieses Programm also nur fürn<=36
.Geben Sie die Matrixelemente in STDIN einfach durch Leerzeichen getrennt an
permanent.cpp
:quelle
2 << (n-1)
am Ende eine Reduzierung vornehmen muss, was bedeutet, dass mein int128-Akku weit vor diesem Zeitpunkt übergelaufen ist.C99, n ≈ 33 (35 Sekunden)
Die Eingabe ist derzeit etwas umständlich; Es werden Zeilen als Befehlszeilenargumente verwendet, wobei jeder Eintrag durch sein Vorzeichen dargestellt wird, dh + zeigt eine 1 an und - zeigt eine -1 an .
Testlauf
quelle
popcnt
). Wenn das Zeit spart, ist die nächste große Hürde der Integer-Typ. Für zufällig erzeugte Matrizen ist die bleibende Zahl vergleichsweise klein. Wenn ich einen einfachen Weg finden kann, um eine Grenze zu berechnen, bevor ich die eigentliche Berechnung durchführe, könnte ich das Ganze in eine große Bedingung einwickeln.Python 2, n ≈ 28
Verwendet die Glynn-Formel mit einem Gray-Code für Aktualisierungen. Läuft
n=23
in einer Minute auf meinem Computer. Dies kann man sicherlich besser in einer schnelleren Sprache und mit besseren Datenstrukturen umsetzen. Dies bedeutet nicht, dass die Matrix einen Wert von ± 1 hat.Eine Ryser-Formelimplementierung ist sehr ähnlich und summiert über alle 0/1-Vektoren von Koeffizienten anstatt über ± 1-Vektoren. Es dauert ungefähr doppelt so lange wie Glynns Formel, da über alle 2 ^ n solche Vektoren addiert werden, wohingegen Glynns Hälften die Symmetrie nur für diejenigen verwenden, die mit beginnen
+1
.quelle
pypy
problemlos gerechnet werdenn=28
. Lembiks System scheint ziemlich schnell zu sein, wenn nicht sogar ein bisschen schneller.Rust + extprim
Dieser einfache Ryser mit Gray-Code-Implementierung benötigt ungefähr
65 bis90 Sekunden, um n = 31 auf meinem Laptop auszuführen.Ich kann mir vorstellen, dass Ihre Maschine in weit unter 60 Jahren dort ankommt.Ich benutze extprim 1.1.1 füri128
.Ich habe Rust noch nie benutzt und weiß nicht, was ich tue. Keine anderen Compileroptionen als die, die dies tun
cargo build --release
. Kommentare / Vorschläge / Optimierungen sind willkommen.Der Aufruf ist identisch mit Dennis 'Programm.
quelle
git clone https://gitlab.com/ezrast/permanent.git; cd permanent; cargo build --release
wenn Sie sicher sein möchten, dass Sie dasselbe Setup wie ich haben. Die Fracht übernimmt die Abhängigkeiten. Binär geht reintarget/release
.Haskell, n = 31 (54 s)
Mit vielen unschätzbaren Beiträgen von @Angs: Verwenden Sie
Vector
, verwenden Sie Kurzschlussprodukte, schauen Sie sich ungerade n an.Meine ersten Parallelitätsversuche in Haskell. Sie können viele Optimierungsschritte im Revisionsverlauf sehen. Erstaunlicherweise waren es meist sehr kleine Änderungen. Der Code basiert auf der Formel im Abschnitt "Balasubramanian-Bax / Franklin-Glynn-Formel" im Wikipedia-Artikel zur Berechnung der bleibenden Karte .
p
berechnet die bleibende. Es heißt überpt
das die Matrix in einer Weise transformiert wird, die immer gültig ist, aber besonders nützlich für die Matrizen, die wir hier erhalten.Kompilieren mit
ghc -O2 -threaded -fllvm -feager-blackholing -o <name> <name>.hs
. So führen Sie mit Parallelisierung, geben Parameter Laufzeit wie folgt aus :./<name> +RTS -N
. Die Eingabe erfolgt wie[[1,2],[3,4]]
im letzten Beispiel von stdin mit durch Kommas getrennten Listen in Klammern (Zeilenumbrüche sind überall zulässig).quelle
Data.Vector
. Die Änderungen vorbehalten Funktionstypen geändert:import qualified Data.Vector as V
,x (V.zipWith(-) p v) vs (-m) c' )
,p (v:vs) = x (foldl (V.zipWith (+)) v vs) (map (V.map (2*)) vs) 1 11
,main = getContents >>= print . p . map V.fromList . read
V.product
). Das gab mir nur ~ 10%. Der Code wurde so geändert, dass die Vektoren nurInt
s enthalten . Das ist in Ordnung, weil sie nur addiert werden, die großen Zahlen stammen aus der Multiplikation. Dann waren es ~ 20%. Ich hatte die gleiche Änderung mit dem alten Code versucht, aber zu diesem Zeitpunkt verlangsamte es es. Ich habe es noch einmal versucht, weil es erlaubt, Vektoren ohne Box zu verwenden , was sehr geholfen hat!x p _ m _ = m * (sum $ V.foldM' (\a b -> if b==0 then Nothing else Just $ a*fromIntegral b) 1 p)
- Produkt als monadische Falte, wobei 0 ein Sonderfall ist. Scheint oft nützlich zu sein.Transversable
(ich sehe, dassproduct
es kein Fehler war, den Esser nicht zu ändern ...) für ghc von zB Debian Stable. Es wird die Form der Eingabe verwendet, aber das scheint in Ordnung zu sein: Wir verlassen uns nicht darauf, sondern optimieren nur dafür. Das Timing ist viel aufregender: Meine zufällige 30x30-Matrix ist etwas schneller als 29x29, aber 31x31 dauert dann 4x. - Dass INLINE bei mir nicht funktioniert. AFAIK wird für rekursive Funktionen ignoriert.product
aber vergessen. Es scheint, als hätten nur gerade Längen Nullen.p
Für ungerade Längen sollten wir das reguläre Produkt anstelle des Kurzschlusses verwenden, um das Beste aus beiden Welten zu erhalten.Mathematica, Nr. 20
Mit dem
Timing
Befehl benötigt eine 20x20-Matrix auf meinem System ungefähr 48 Sekunden. Dies ist nicht genau so effizient wie das andere, da es auf der Tatsache beruht, dass die bleibende Karte als der Koeffizient des Produkts von Polymomen aus jeder Reihe der Matrix gefunden werden kann. Eine effiziente Polynommultiplikation wird durchgeführt, indem die Koeffizientenlisten erstellt und eine Faltung unter Verwendung von durchgeführt werdenListConvolve
. Dies erfordert ungefähr 0 (2 n n 2 ) Zeit, vorausgesetzt, die Faltung wird unter Verwendung einer Fast Fourier-Transformation oder ähnlichem durchgeführt, was 0 ( n log n ) Zeit erfordert .quelle
Python 2, n = 22 [Referenz]
Dies ist die 'Referenz'-Implementierung, die ich gestern mit Lembik geteilt habe
n=23
auf seiner Maschine um einige Sekunden , auf meiner Maschine dauert es ungefähr 52 Sekunden. Um diese Geschwindigkeiten zu erreichen, müssen Sie dies über PyPy ausführen.Die erste Funktion berechnet die bleibende Zahl ähnlich wie die Determinante berechnet werden könnte, indem Sie jede Untermatrix durchgehen, bis Sie ein 2x2 übrig haben, auf das Sie die Grundregel anwenden können. Es ist unglaublich langsam .
Die zweite Funktion implementiert die Ryser-Funktion (die zweite in Wikipedia aufgeführte Gleichung). Die Menge
S
ist im Wesentlichen die Potenz der Zahlen{1,...,n}
(variabels_list
im Code).quelle
RPython 5.4.1, n ≈ 32 (37 Sekunden)
Laden Sie zum Kompilieren die neueste PyPy-Quelle herunter und führen Sie Folgendes aus:
Die resultierende ausführbare Datei wird benannt
matrix-permanent-c
im aktuellen Arbeitsverzeichnis oder ähnelt diesen.Ab PyPy 5.0 sind RPythons Threading-Primitive viel weniger primitiv als früher. Neu erzeugte Threads erfordern die GIL, die für parallele Berechnungen praktisch unbrauchbar ist. Ich habe
fork
stattdessen verwendet, so dass es unter Windows möglicherweise nicht wie erwartet funktioniert,obwohl ich dasKompilieren (unresolved external symbol _fork
)nicht erfolgreich getestet habe.Die ausführbare Datei akzeptiert bis zu zwei Befehlszeilenparameter. Der erste ist die Anzahl der Threads, der zweite optionale Parameter ist
n
. Wenn es bereitgestellt wird, wird eine zufällige Matrix generiert, andernfalls wird es von stdin gelesen. Jede Zeile muss durch Zeilenumbrüche (ohne abschließende Zeilenumbrüche) getrennt sein und jeder Wert muss durch Leerzeichen getrennt sein. Die dritte Beispieleingabe würde wie folgt lauten:Beispielnutzung
Methode
Ich habe die Balasubramanian-Bax / Franklin-Glynn-Formel mit einer Laufzeitkomplexität von O (2 n n) verwendet . Anstatt jedoch das δ in grauer Codereihenfolge zu iterieren , habe ich stattdessen die Vektorzeilenmultiplikation durch eine einzelne xor-Operation ersetzt (Abbildung (1, -1) → (0, 1)). Die Vektorsumme kann ebenfalls in einer einzigen Operation gefunden werden, indem n minus das Doppelte der Popcount-Zahl genommen wird.
quelle
Schläger 84 Bytes
Die folgende einfache Funktion funktioniert für kleinere Matrizen, hängt aber für größere Matrizen an meiner Maschine:
Ungolfed:
Der Code kann leicht für eine ungleiche Anzahl von Zeilen und Spalten geändert werden.
Testen:
Ausgabe:
Wie oben erwähnt, hängt es beim Testen von Folgendem ab:
quelle