1 # Heap: a priority queue, as a skew heap ordered by a le you pass in.
2 #
3 # The smallest element (by le) sits at the top. push and pop are
4 # amortized O(log n) over a heap used once (sharing one with + and popping
5 # both copies can cost more), peek and size are O(1), and sort is
6 # heapsort. le is a template, as in List.sort: Heap.push(~U32, ~U32.is_le,
7 # 7, h).
8 #
9 # The laws at the bottom are checked every time this file is imported.
10 import Base
11
12 type Heap<-A: Data> is Data:
13 HLeaf{}
14 HNode{size: Nat, top: A, left: Heap, right: Heap}
15
16 def Heap.new(-A: Data) -> Heap:
17 HLeaf{}
18
19 def Heap.size(-A: Data, h: Heap) -> Nat:
20 match h:
21 case HLeaf{}:
22 0n
23 case HNode{n, x, l, r}:
24 n
25
26 def Heap.peek(-A: Data, h: Heap) -> Maybe<&2, A>:
27 match h:
28 case HLeaf{}:
29 None{}
30 case HNode{n, x, l, r}:
31 Some{x}
32
33 # The smaller root wins: its right child merges with the other heap and
34 # becomes the left, and its old left becomes the right (a skew heap's
35 # swap, which keeps merges short on average). The fuel is the size of both
36 # heaps: every step takes a root out, so it never runs short.
37 def Heap.merge.go(
38 ~A: Data, ~le: A -> A -> Bool, fuel: Nat, a: Heap, b: Heap
39 ) -> Heap:
40 match fuel:
41 case 0n:
42 a
43 case 1n+f:
44 match a b:
45 case HLeaf{} HLeaf{}:
46 HLeaf{}
47 case HLeaf{} HNode{nb, y, bl, br}:
48 HNode{nb, y, bl, br}
49 case HNode{na, x, al, ar} HLeaf{}:
50 HNode{na, x, al, ar}
51 case HNode{+na, +x, +al, +ar} HNode{+nb, +y, +bl, +br}:
52 +c = le(x, y)
53 HNode{Nat.add(na, nb), Bool.pick(A, c, x, y),
54 Heap.merge.go(~A, ~le, f,
55 Bool.pick(Heap, c, ar, br),
56 Bool.pick(Heap, c, HNode{nb, y, bl, br},
57 HNode{na, x, al, ar})),
58 Bool.pick(Heap, c, al, bl)}
59
60 def Heap.merge(
61 ~A: Data, ~le: A -> A -> Bool, +a: Heap, +b: Heap
62 ) -> Heap:
63 Heap.merge.go(~A, ~le, Nat.add(Heap.size(A, a), Heap.size(A, b)), a, b)
64
65 def Heap.push(~A: Data, ~le: A -> A -> Bool, x: A, h: Heap) -> Heap:
66 Heap.merge(~A, ~le, HNode{1n, x, HLeaf{}, HLeaf{}}, h)
67
68 # the top, and the heap without it; None on an empty heap
69 def Heap.pop(
70 ~A: Data, ~le: A -> A -> Bool, h: Heap
71 ) -> Maybe<&1, A & Heap>:
72 match h:
73 case HLeaf{}:
74 None{}
75 case HNode{n, x, l, r}:
76 Some{(x, Heap.merge(~A, ~le, l, r))}
77
78 def Heap.from_list(~A: Data, ~le: A -> A -> Bool, xs: List<&2, A>) -> Heap:
79 match xs:
80 case Nil{}:
81 HLeaf{}
82 case h <> t:
83 Heap.push(~A, ~le, h, Heap.from_list(~A, ~le, t))
84
85 def Heap.to_list.go(
86 ~A: Data, ~le: A -> A -> Bool, fuel: Nat, h: Heap
87 ) -> List<&2, A>:
88 match fuel:
89 case 0n:
90 Nil{}
91 case 1n+f:
92 match h:
93 case HLeaf{}:
94 Nil{}
95 case HNode{n, x, l, r}:
96 x <> Heap.to_list.go(~A, ~le, f, Heap.merge(~A, ~le, l, r))
97
98 # the elements, smallest first
99 def Heap.to_list(~A: Data, ~le: A -> A -> Bool, +h: Heap) -> List<&2, A>:
100 Heap.to_list.go(~A, ~le, Heap.size(A, h), h)
101
102 # heapsort
103 def Heap.sort(~A: Data, ~le: A -> A -> Bool, xs: List<&2, A>) -> List<&2, A>:
104 Heap.to_list(~A, ~le, Heap.from_list(~A, ~le, xs))
105
106 # Laws
107 # ----
108
109 # LAW: popping what was pushed onto an empty heap gives it back
110 law Heap.pop.push_new:
111 for -x: U32
112 {Heap.pop(~U32, ~U32.is_le, Heap.push(~U32, ~U32.is_le, x, Heap.new(U32)))
113 == Some{(x, Heap.new(U32))} : Maybe<&1, U32 & Heap>}
114
115 def Heap.pop.push_new(x):
116 {==}
117
118 # LAW: a push adds one to the size
119 law Heap.size.push:
120 for -x: U32
121 for h: Heap
122 {Heap.size(U32, Heap.push(~U32, ~U32.is_le, x, h))
123 == 1n+Heap.size(U32, h) : Nat}
124
125 def Heap.size.push(x, h):
126 match h:
127 case HLeaf{}:
128 {==}
129 case HNode{n, y, l, r}:
130 {==}