Wie funktionieren Inits und Tails in Data.Sequence?

8

Louis Wasserman hat die aktuellen Implementierungen von inits und tails in Data.Sequence geschrieben. Er weist darauf hin, dass sie sehr effizient sind, und wenn man sich den Code anschaut, kann ich sehen, dass alles, was sie tun, sauber und von oben nach unten läuft, was zu guten Leistungen für faule Bäume führt. Leider kann ich nicht wirklich Kopf oder Zahl davon machen, was sie gerade machen. Kann mir jemand helfen? Der Code ist ein bisschen lang, aber kann auf Hackage .

    
dfeuer 06.03.2015, 20:05
quelle

1 Antwort

7

Ich bin es!

Ich denke, der beste Ansatz ist wahrscheinlich, ein Beispiel zu erarbeiten. Lass uns gehen ...

%Vor%

Hier ist eine Sequenz, etwas vereinfacht (z. B. die Elem-Wrapper auslassen).

Lassen Sie uns in diesem Zusammenhang handeln; Schwänze sind im Wesentlichen symmetrisch. Unser rekursiver Algorithmus wird die leere Init auslassen und nur nichtleere Dinge einschließen.

Präfix

Also wird die Präfixziffer von inits mit im wesentlichen fmap digitToTree (initsDigit (Two 1 2)) erzeugt.

%Vor%

Das sind also die ersten beiden Eingänge des Ganzen, und diese Ziffer wird die Präfixziffer des Ergebnisses von inits sein. (Abgesehen davon, dass wir die leere Sequenz voranstellen werden, nachdem wir fertig sind, aber lasst uns das jetzt ignorieren.)

Innerer Baum

Nehmen wir nun die Eingänge des inneren Baums, die als FingerTree (Node a) behandelt werden - wir werden die Knoten noch nicht auseinanderziehen, es ist nur ein Zwei-Element FingerTree mit zwei Knoten. Ich werde nicht die Details dazu machen, es wird nur durch den gleichen Algorithmus rekursiv, ich werde einfach magisch das Ergebnis erreichen

%Vor%

Das sind also die Eingänge des inneren Baumes. Wie stimmen diese mit den Inits des äußeren Baumes überein? Jede Init des inneren Baums entspricht einem Baum, der

enthält
  • die Präfixziffer des ursprünglichen Baums, Two 1 2
  • alle bis auf die letzte Node der Init des inneren Baums
  • ein Präfix des letzten Node der Init des inneren Baums

So wird jedes FingerTree (Node a) , das durch eine Init des inneren Baums gewonnen wurde, auf ein Node (FingerTree a) abgebildet, das ein FingerTree für jede Init des letzten Knotens in FingerTree (Node a) enthält.

Zum Beispiel wird Single (Node2 3 4) mit dem letzten extrahierten Knoten in Empty und Node2 3 4 zerlegt, und die resultierende Node (FingerTree a) ist

%Vor%

Und für das andere Präfix des inneren Baums, Deep (One (Node2 3 4)) Empty (One (Node2 5 6)) , ergibt das Extrahieren des letzten Node den Rest Single (Node2 3 4) und den extrahierten Knoten Node2 5 6 , also den resultierenden 'Knoten (FingerTree a)

%Vor%

Das ist also eine Operation, die ein FingerTree (Node a) , eine einzelne Init des inneren Baums, zu einem Node (FingerTree a) führt. Wenn wir also die Inits des inneren Baums rekursiv als FingerTree (FingerTree (Node a)) erfasst haben, ordnen wir diese Funktion ihnen zu, um ein FingerTree (Node (FingerTree a)) zu erhalten, was genau das ist, was wir wollten; es ist der innere Baum der Innereien des Ganzen.

Suffix

Schließlich gibt es Einträge des ursprünglichen Baums, die aus

bestehen
  • das ursprüngliche Präfix
  • der ursprüngliche innere Baum
  • jedes Init des Suffix des ursprünglichen Baums

und diese werden die Suffixziffer des Baumes von inits. initsDigit (Two 7 8) gibt Two (One 7) (Two 7 8) zurück, und wir ordnen im Wesentlichen nur \sf -> Deep pr m sf zu, um

zu erhalten %Vor%

Also, das ist nicht so, wie der Code organisiert ist. Wir haben eine Funktion von FingerTree a bis FingerTree (FingerTree a) beschrieben, aber die tatsächliche Implementierung ist im Wesentlichen die plus fmap , weil wir die Elemente immer irgendwie zuordnen müssen - selbst wenn sie nur neue Typen umhüllen. Aber das machen wir im Grunde.

    
Louis Wasserman 09.03.2015, 20:49
quelle