Nachdem ich einige Zeit mit 2-3 Fingerbäumen gearbeitet habe, war ich bei den meisten Operationen von deren Geschwindigkeit beeindruckt. Das einzige Problem, auf das ich gestoßen bin, ist der große Aufwand, der mit der anfänglichen Erstellung eines großen Fingerbaums verbunden ist. Da das Erstellen als eine Folge von Verkettungsvorgängen definiert ist, wird eine große Anzahl nicht benötigter Fingerbaumstrukturen erstellt.
Aufgrund der Komplexität von 2-3 Fingerbäumen sehe ich keine intuitive Methode, um sie zu booten, und alle meine Suchanfragen sind leer. Die Frage ist also, wie Sie einen 2-3-Finger-Baum mit minimalem Overhead booten können.
Um es deutlich zu machen: Wenn eine Sequenz bekannter Länge n gegeben ist, wird die Fingerbaumdarstellung von S erzeugt mit minimalen Operationen erzeugt.
Der naive Weg, dies zu erreichen, sind aufeinanderfolgende Aufrufe der Cons-Operation (in der Literatur der ' -Operator). Dies erzeugt jedoch n verschiedene Fingerbaumstrukturen, die alle Schichten von S für [ 1 .. i ] darstellen .
quelle
Antworten:
GHC derO ( lgn )
Data.Sequence
‚s -replicate
Funktion baut eine fingertree in Zeit und Raum, aber dies zu wissen , die Elemente aktiviert ist , die von dem get-go auf der rechten Seite der Wirbelsäule des Finger Baum gehen. Diese Bibliothek wurde von den Autoren des Originalpapiers auf 2-3 Fingerbäumen geschrieben.Wenn Sie einen Fingerbaum durch wiederholte Verkettung erstellen möchten, können Sie möglicherweise den vorübergehenden Platzbedarf beim Erstellen reduzieren, indem Sie die Darstellung der Stacheln ändern. Die Stacheln von 2-3 Fingerbäumen werden geschickt als synchronisierte, einfach verknüpfte Listen gespeichert. Wenn Sie die Stacheln stattdessen als Deques speichern, kann beim Verketten von Bäumen möglicherweise Platz gespart werden. Die Idee ist, dass die Verkettung von zwei Bäumen gleicher Höhe Platz beansprucht, indem die Stacheln der Bäume wiederverwendet werden. Wenn 2-3 Fingerbäume wie ursprünglich beschrieben verkettet werden, können die Stacheln, die sich innerhalb des neuen Baums befinden, nicht mehr unverändert verwendet werden.O ( 1 )
Kaplans und Tarjans "rein funktionale Darstellungen verketteter sortierter Listen" beschreiben eine kompliziertere Fingerbaumstruktur . In diesem Artikel (in Abschnitt 4) wird auch eine Konstruktion erörtert, die der oben von mir gemachten Deque-Anregung ähnelt. Ich glaube, die Struktur, die sie beschreiben, kann zwei Bäume gleicher Höhe in O verketten ( 1 )O ( 1 ) Raum und Zeit . Ist das genug Platz für Sie, um Fingerbäume zu bauen?
NB: Ihre Verwendung des Wortes "Bootstrapping" bedeutet etwas anderes als Ihre obige Verwendung. Sie bedeuten, einen Teil einer Datenstruktur mit einer einfacheren Version derselben Struktur zu speichern.
quelle
Als ich mich auf die ausgezeichnete Antwort von jbapple bezog
replicate
, aber stattdessenreplicateA
(auf der aufgebautreplicate
ist) verwendete, kam ich auf Folgendes:myFromList
(in einer etwas effizienteren Version) ist bereits definiert und wird intern in verwendetData.Sequence
für den Bau von Finger Bäumen , die Ergebnisse der Sorten sind.Im Allgemeinen ist die Intuition für
replicateA
einfach.replicateA
basiert auf der Funktion applicativeTree .applicativeTree
Nimmt ein Stück eines Baumes einer Größem
und erzeugt einen ausgewogenen Baum, dern
Kopien davon enthält. Die Fälle fürn
bis zu 8 (einDeep
Finger) sind fest codiert. Alles darüber, und es ruft sich rekursiv auf. Das "anwendbare" Element besteht einfach darin, dass es die Konstruktion des Baums mit Threading-Effekten wie im Fall des obigen Codes mit dem Zustand verschachtelt.Die
go
Funktion, die repliziert wird, ist einfach eine Aktion, die den aktuellen Status abruft, ein Element von der Oberseite entfernt und den Rest ersetzt. Bei jedem Aufruf wird die als Eingabe bereitgestellte Liste weiter nach unten verschoben.Noch einige konkrete Hinweise
Bei einigen einfachen Tests ergab sich ein interessanter Kompromiss bei der Leistung. Die obige Hauptfunktion lief mit myFromList fast 1/3 niedriger als mit
fromList
. Auf der anderen SeitemyFromList
wird ein konstanter Heap von 2 MB verwendet, während der StandardfromList
bis zu 926 MB verwendet. Diese 926 MB entstehen, wenn die gesamte Liste auf einmal gespeichert werden muss. In der Zwischenzeit ist die Lösung in dermyFromList
Lage, die Struktur in einer trägen Streaming-Art und Weise zu konsumieren. Das Problem mit der Geschwindigkeit ergibt sich aus der Tatsache, dassmyFromList
etwa doppelt so viele Allokationen (als Folge des Aufbaus / der Zerstörung der Staatsmonade) durchgeführt werden müssen wiefromList
. Wir können diese Zuordnungen beseitigen, indem wir zu einer CPS-transformierten Zustandsmonade übergehen. Dies führt jedoch dazu, dass zu einem bestimmten Zeitpunkt weitaus mehr Speicherplatz zur Verfügung steht, da der Verlust der Faulheit erfordert, die Liste ohne Streaming zu durchlaufen.Wenn ich dagegen nicht die gesamte Sequenz mit einer Show
myFromList
forciere , sondern nur den Kopf oder das letzte Element extrahiere, wird sofort ein größerer Gewinn erzielt - das Extrahieren des Kopfelements erfolgt fast augenblicklich und das Extrahieren des letzten Elements beträgt 0,8s . WährenddessenfromList
kostet das Extrahieren des Kopfes oder des letzten Elements mit dem Standard ~ 2,3 Sekunden.Dies sind alles Details und sind eine Folge von Reinheit und Faulheit. In einer Situation mit Mutation und wahlfreiem Zugriff stelle ich mir die
replicate
Lösung strikt besser vor.Es wirft jedoch die Frage auf, ob es eine Möglichkeit gibt, eine
applicativeTree
solche umzuschreiben ,myFromList
die strikt effizienter ist. Ich denke, das Problem ist, dass die anwendbaren Aktionen in einer anderen Reihenfolge ausgeführt werden als der Baum, aber ich habe nicht vollständig durchgearbeitet, wie dies funktioniert oder ob es eine Möglichkeit gibt, dies zu beheben.quelle
fromList
wenn die gesamte Sequenz erzwungen wird. (2) Vielleicht ist diese Antwort für cstheory.stackexchange.com zu code-lastig und zu sprachabhängig. Es wäre toll, wenn Sie eine sprachunabhängige Erklärung hinzufügen könntenreplicateA
.Während Sie mit einer großen Anzahl von zwischenliegenden Fingerbaumstrukturen fertig werden, teilen sie den größten Teil ihrer Struktur miteinander. Am Ende reservieren Sie höchstens doppelt so viel Speicher wie im Idealfall, und der Rest wird mit der ersten Sammlung freigegeben. Die Asymptotik ist die gleiche, wie sie nur sein kann, da Sie am Ende einen mit n Werten gefüllten Fingerbaum benötigen.
Sie können den Fingerbaum mit
Data.FingerTree.replicate
und erstellen, indem SieFingerTree.fmapWithPos
Ihre Werte in einem Array nachschlagen, das die Rolle Ihrer endlichen Sequenz spielt, oder indem SietraverseWithPos
sie aus einer Liste oder einem anderen Container bekannter Größe entfernen.replicateA
mapAccumL
TL; DR Wenn ich das tun müsste, würde ich wahrscheinlich verwenden:
und Index in eine feste Größe Array würde ich nur liefern
(arr !)
fürf
oben.quelle