Erstens sind Listen eine Art Bäume. Wenn wir eine Liste als verknüpfte Liste darstellen , handelt es sich nur um einen Baum, dessen Knoten entweder 1 oder 0 Nachkommen haben.
Durchsuchen von Bäumen ist nur eine Verwendung von Bäumen als Datenstruktur. Bäume haben viele verschiedene Anwendungen in der Informatik, einschließlich Sortieren, Implementieren von Karten, assoziativen Arrays usw.
Im Allgemeinen sind Listen, Bäume usw. rekursive Datenstrukturen: Jeder Knoten enthält einige Informationen und eine weitere Instanz derselben Datenstruktur. Das Falten ist eine Operation über alle derartigen Strukturen, die Knoten rekursiv in Werte "von unten nach oben" umwandelt. Das Entfalten ist der umgekehrte Vorgang, es wandelt Werte in Knoten "von oben nach unten" um.
Für eine gegebene Datenstruktur können wir ihre Falt- und Entfaltungsfunktionen mechanisch konstruieren.
Nehmen wir als Beispiel Listen. (Ich werde Haskell für die Beispiele verwenden, da es geschrieben ist und seine Syntax sehr klar ist.) Liste ist entweder ein Ende oder ein Wert und ein "Ende".
data List a = Nil | Cons a (List a)
Stellen wir uns jetzt vor, wir falten eine Liste. Bei jedem Schritt muss der aktuelle Knoten gefaltet werden, und die rekursiven Unterknoten wurden bereits gefaltet. Wir können diesen Zustand als darstellen
data ListF a r = NilF | ConsF a r
wobei r
der Zwischenwert konstruiert , indem die Unterliste zu falten. Dies ermöglicht es uns, eine Faltungsfunktion über Listen auszudrücken:
foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil = f NilF
foldList f (Cons x xs) = f (ConsF x (foldList f xs))
Wir konvertieren List
in, ListF
indem wir ihre Unterliste rekursiv umklappen und dann eine in definierte Funktion verwenden ListF
. Wenn Sie darüber nachdenken, ist dies nur eine weitere Darstellung von Standard foldr
:
foldr :: (a -> r -> r) -> r -> List a -> r
foldr f z = foldList g
where
g NilF = z
g (ConsF x r) = f x r
Wir können unfoldList
auf die gleiche Weise konstruieren :
unfoldList :: (r -> ListF a r) -> r -> List a
unfoldList f r = case f r of
NilF -> Nil
ConsF x r' -> Cons x (unfoldList f r')
Wieder ist es nur eine andere Darstellung von unfoldr
:
unfoldr :: (r -> Maybe (a, r)) -> r -> [a]
(Beachten Sie, dass Maybe (a, r)
isomorph zu ist ListF a r
.)
Und wir können auch eine Entwaldungsfunktion konstruieren:
deforest :: (ListF a r -> r) -> (s -> ListF a s) -> s -> r
deforest f u s = f (map (deforest f u) (u s))
where
map h NilF = NilF
map h (ConsF x r) = ConsF x (h r)
Es lässt einfach das Zwischenprodukt weg List
und verschmilzt die Falt- und Entfaltungsfunktionen miteinander.
Das gleiche Verfahren kann auf jede rekursive Datenstruktur angewendet werden. Beispiel: Ein Baum, dessen Knoten 0, 1, 2 oder Nachkommen mit Werten auf 1- oder 0-Verzweigungsknoten haben können:
data Tree a = Bin (Tree a) (Tree a) | Un a (Tree a) | Leaf a
data TreeF a r = BinF r r | UnF a r | LeafF a
treeFold :: (TreeF a r -> r) -> Tree a -> r
treeFold f (Leaf x) = f (LeafF x)
treeFold f (Un x r) = f (UnF x (treeFold f r))
treeFold f (Bin r1 r2) = f (BinF (treeFold f r1) (treeFold f r2))
treeUnfold :: (r -> TreeF a r) -> r -> Tree a
treeUnfold f r = case f r of
LeafF x -> Leaf x
UnF x r -> Un x (treeUnfold f r)
BinF r1 r2 -> Bin (treeUnfold f r1) (treeUnfold f r2)
Natürlich können wir deforestTree
genauso mechanisch wie bisher erstellen .
(Normalerweise würden wir treeFold
bequemer ausdrücken als:
treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r
)
Ich werde die Details weglassen, ich hoffe, das Muster ist offensichtlich.
Siehe auch: