-
Notifications
You must be signed in to change notification settings - Fork 0
/
tree-diff.fsx
96 lines (80 loc) · 2.83 KB
/
tree-diff.fsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Tree-Diff implementation
type Change<'T> =
| Unchanged
| Inserted
| Deleted
| Updated of 'T
type Node<'T> = {
Id : int
Value : 'T
Modified : Change<'T>
Children : Node<'T> list
}
/// Nodes are equal in all but Value
let (==) node1 node2 =
node1.Id = node2.Id
&& node1.Children = node2.Children
&& node1.Value <> node2.Value
/// Combines the children of two nodes into a single list
let combine node1 node2 =
let first = node1.Children |> List.map (fun x -> (Some x, node2.Children |> List.tryFind (fun y -> y.Id = x.Id)))
let second = node2.Children |> List.map (fun x -> (node1.Children |> List.tryFind (fun y -> y.Id = x.Id)), Some x)
first @ second |> List.distinct
let rec markNode change node =
{ node with
Modified = change
Children = node.Children |> List.map (markNode change) }
let insert node = markNode Inserted node
let delete node = markNode Deleted node
let update node value =
{ node with
Modified = Updated value }
let rec diff (oldNodeOpt, newNodeOpt) =
match oldNodeOpt, newNodeOpt with
| Some oldNode, None -> delete oldNode
| None, Some newNode -> insert newNode
| Some oldNode, Some newNode when oldNode = newNode -> oldNode
| Some oldNode, Some newNode when oldNode == newNode -> update oldNode newNode.Value
| Some oldNode, Some newNode ->
{ oldNode with
Modified = if oldNode.Value = newNode.Value then Unchanged else Updated newNode.Value
Children =
combine oldNode newNode
|> List.map diff }
| None, None -> failwith "Must have at least one node to compare."
/// Compares two trees and returns a tree with the differences labeled
let compare oldTree newTree = diff (Some oldTree, Some newTree)
// -- Tests -- //
let printTree tree =
let rec traverse node level =
let spacing = List.init (level * 4) (fun _ -> " ") |> String.concat ""
let nodeString = sprintf "(%i) %s - %A" node.Id node.Value node.Modified
printfn "%s%s" spacing nodeString
node.Children
|> List.iter (fun child -> traverse child (level + 1))
traverse tree 0
let tree0 = {
Id = 0
Value = "Root"
Modified = Unchanged
Children = [
{ Id = 1; Value = "A"; Modified = Unchanged; Children = [
{ Id = 3; Value = "D"; Modified = Unchanged; Children = [
{ Id = 4; Value = "E"; Modified = Unchanged; Children = [] }
] }
] }
{ Id = 2; Value = "B"; Modified = Unchanged; Children = [] }
]
}
let tree1 = {
Id = 0
Value = "Root"
Modified = Unchanged
Children = [
{ Id = 2; Value = "C"; Modified = Unchanged; Children = [
{ Id = 3; Value = "D"; Modified = Unchanged; Children = [] }
] }
]
}
compare tree0 tree1
|> printTree