Ich bin daran interessiert, eine effiziente Haskell-Funktion zu schreiben triangularize :: [a] -> [[a]]
, die eine (möglicherweise unendliche) Liste in eine Liste von Listen "trianguliert". Zum Beispiel triangularize [1..19]
sollte zurückkehren
[[1, 3, 6, 10, 15]
,[2, 5, 9, 14]
,[4, 8, 13, 19]
,[7, 12, 18]
,[11, 17]
,[16]]
Mit effizient meine ich, dass ich möchte, dass es O(n)
rechtzeitig ausgeführt wird, wo n
die Länge der Liste ist.
Beachten Sie, dass dies in einer Sprache wie Python recht einfach ist, da das Anhängen an das Ende einer Liste (eines Arrays) eine konstante Zeitoperation ist. Eine sehr wichtige Python-Funktion, die dies erreicht, ist:
def triangularize(elements):
row_index = 0
column_index = 0
diagonal_array = []
for a in elements:
if row_index == len(diagonal_array):
diagonal_array.append([a])
else:
diagonal_array[row_index].append(a)
if row_index == 0:
(row_index, column_index) = (column_index + 1, 0)
else:
row_index -= 1
column_index += 1
return diagonal_array
Dies ist darauf zurückzuführen, dass ich Haskell verwendet habe, um einige "tabl" -Sequenzen in der Online-Enzyklopädie der ganzzahligen Sequenzen (OEIS) zu schreiben , und ich möchte in der Lage sein, eine gewöhnliche (eindimensionale) Sequenz in eine (2-) Sequenz umzuwandeln dimensionale) Sequenz von Sequenzen auf genau diese Weise.
Vielleicht gibt es eine clevere (oder nicht so clevere) Möglichkeit, foldr
die Eingabeliste zu überschreiten, aber ich konnte sie nicht klären.
quelle
foldr
Sie esunfoldr (Just . combWith comb)
für unendliche Listen mögen . Leider ist, wie ich unter meiner Antwort erwähnt habe,combWith
O (n), daher ist die akzeptierte AntwortverwendungsplitAt
wesentlich effizienter.Antworten:
Machen Sie immer größere Stücke:
Dann transponieren Sie einfach zweimal:
Probieren Sie es in ghci:
quelle
transpose
O (n) zu sein. Ich bin auch nicht sehr zuversichtlich, dass dies nicht der Fall ist - die Implementierung ist etwas kompliziert!take 3 . map (take 3) . diagonalize $ [1..]
gibt[[1,3,6],[2,5,9],[4,8,13]]
, was in Ordnung scheint.take 10 $ map (take 10) $ diagonalize [1..]
in der Tat gibt die ersten zehn Elemente der ersten zehn Zeilen.Dies scheint in direktem Zusammenhang mit dem Argument der Mengenlehre zu stehen, das beweist, dass die Menge der Ganzzahlpaare eins zu eins mit der Menge der Ganzzahlen ( denumerierbar ) übereinstimmt . Das Argument beinhaltet eine sogenannte Cantor-Pairing-Funktion .
Lassen Sie uns aus Neugier sehen, ob wir auf diese Weise eine
diagonalize
Funktion erhalten können. Definieren Sie die unendliche Liste der Cantor-Paare rekursiv in Haskell:Und versuchen Sie das in ghci:
Wir können die Paare nummerieren und zum Beispiel die Zahlen für diejenigen Paare extrahieren, die eine Null-x-Koordinate haben:
Wir erkennen, dass dies die oberste Zeile aus dem Ergebnis des OP im Text der Frage ist. Ähnliches gilt für die nächsten beiden Zeilen:
Von dort aus können wir unseren ersten Entwurf einer
diagonalize
Funktion schreiben :BEARBEITEN: Leistungsaktualisierung
Bei einer Liste mit 1 Million Elementen beträgt die Laufzeit 18 Sekunden und bei 4 Millionen Elementen 145 Sekunden. Wie von Redu erwähnt, scheint dies eine Komplexität von O (n√n) zu sein.
Das Verteilen der Paare auf die verschiedenen Zielunterlisten ist ineffizient, da die meisten Filtervorgänge fehlschlagen.
Um die Leistung zu verbessern, können wir eine Data.Map-Struktur für die Zielunterlisten verwenden.
Mit dieser zweiten Version scheint die Leistung viel besser zu sein: 568 ms für die Liste mit 1 Million Artikeln, 2669 ms für die Liste mit 4 Millionen Artikeln. Es liegt also nahe an der O (n * Log (n)) -Komplexität, auf die wir gehofft haben könnten.
quelle
Es könnte eine gute Idee sein, einen
comb
Filter zu knacken .Was macht
comb
Filter also? Es ist wie ,splitAt
aber statt Spaltung an einem einzigen indizieren Art von Reißverschluss die gegebene unendliche Liste mit dem angegebenen Kamm der Elemente coressponding zu trennenTrue
undFalse
auf dem Kamm. So dass;Jetzt müssen wir nur noch unsere unendliche Liste kämmen und
fst
die erste Zeile nehmen und diesnd
mit derselben weiter kämmencomb
.Machen wir das;
scheint auch faul zu sein :)
Ich denke, die Komplexität könnte wie O (n√n) sein, aber ich kann nicht sicher sein. Irgendwelche Ideen..?
quelle
:set +s
. @Daniel Wagners akzeptierte Antwort scheint mit dem Listentyp ziemlich schnell zu laufen. Könnten Sie bitte überprüfen, wie es mit Ihrem verglichen wird? Ich hatte gehofft, eine ähnliche Leistung zu erzielen, aber dascombWith
ist nirgends so schnell wiespilitAt
.transpose
scheinen unbegründet zu sein. Darüber hinaus wirkt es fauler als die Cantor-Karte. Gut gemacht !snd
vonsplitAt
'in O (1) erhalten, aber der Wertfst
sollte immer noch O (n) sein. Irgendwie spiegelt sich dies in der Gesamtleistung als O (nlogn) wider.splitAt
anstatt anzurufendrop
undtake
separat zu verwenden.