Diffing and merging trees
The Tree.Diff
module offers datastructures and function to handle diffing and
merging trees. A diff represents the abstract action that need to be taken to go
from one tree to another. Merging trees allows actually executing these actions.
Either nothing changed, and we can keep a (sub)tree, or something changed.
When something changed, there are essentially 2 cases:
Replace
the
left with the right tree.In case there was a different with the children, there is also the possibility
that the length was different. This is described in the Tail
of the Copy
.
If the left node had more children than the right now, we get a Left
tail
with the trailing children from the left tree.
On the other hand, if the right node had more children we get a Right
tail.
If both trees has the same number of children, the tail is Empty
.
diff : Tree a -> Tree a -> Diff a
Diffing 2 trees (using standard equivalence (==)
) produces a Diff
!
import Tree.Diff as Diff
import Tree exposing (tree, singleton)
Diff.diff
(tree "root"
[ tree "folder"
[ singleton "foo"
, singleton "bar"
]
, singleton "yeah"
, singleton "keep me!"
]
)
(tree "root"
[ tree "folder"
[ singleton "foo" ]
, tree "folder2"
[ singleton "nice" ]
, singleton "keep me!"
]
)
--> Diff.Copy "root"
--> [ Diff.Copy "folder"
--> [ Diff.Keep (singleton "foo") ]
--> (Diff.Left [ singleton "bar" ])
--> , Diff.Replace
--> (singleton "yeah")
--> (tree "folder2" [ singleton "nice" ])
--> , Diff.Keep (singleton "keep me!")
--> ]
--> Diff.Empty
diffWith : (a -> a -> Basics.Bool) -> Tree a -> Tree a -> Diff a
Diff using custom equivalence.
This allows using a custom function to decide whether two labels are really equivalent. Perhaps you're using some custom datatype and you consider two instances of them to be equivalent if they hold the same data, regardless of their structural equality? Or perhaps your labels are floats, and you want to check using some epsilon value?
This is your function!
diffBy : (a -> b) -> Tree a -> Tree a -> Diff a
Diff using regular equality on a derived property of the label.
This is related to diffWith
in the same way List.sortBy
is related to
List.sortWith
. Imagine, for example, that your labels are tuples and you're
only interested in the second value.
You could either write diffWith (\(_, x) (_, y) -> x == y) left right
or the
equivalent but much simple diffBy Tuple.second
.
If you find yourself being worried about performance: Please benchmark!
Note: Merging trees according to the diff structure described here using
regular equality on the labels will always result in the second tree being
returned. For that reason, only mergeWith
and mergeBy
exist: merge a b = b
feels like a silly function to offer!
mergeWith : (a -> a -> Basics.Bool) -> Tree a -> Tree a -> Tree a
mergeBy : (a -> b) -> Tree a -> Tree a -> Tree a
Has the same relation to mergeWith
as diffBy
has to diffWith
.
import Tree.Diff as Diff
import Tree exposing (tree, singleton)
Diff.mergeBy Tuple.second
(tree ( 1, "root" )
[ tree ( 1, "folder" )
[ singleton ( 1, "foo" )
, singleton ( 1, "bar" )
]
, singleton ( 1, "yeah" )
, singleton ( 1, "keep me!" )
]
)
(tree ( 2, "root" )
[ tree ( 2, "folder" )
[ singleton ( 2, "foo" ) ]
, tree ( 2, "folder2" )
[ singleton ( 2, "nice" ) ]
, singleton ( 2, "keep me!" )
]
)
--> tree ( 1, "root" )
--> [ tree ( 1, "folder" )
--> [ singleton ( 1, "foo" ) ]
--> , tree ( 2, "folder2" )
--> [ singleton ( 2, "nice" ) ]
--> , singleton ( 1, "keep me!" )
--> ]