Skip to content

Commit e6a82e2

Browse files
Mutable dynamic arrays, heaps, and simple union find data structures (#585)
For writing examples, I wanted to have some standard mutable data structures, currently: - dynamic/resizable arrays - min-heap (=> priority queue) - union-find on integers They all still need more documentation, testing and maybe some additional functions (I only implemented what I needed). --------- Co-authored-by: Jiří Beneš <[email protected]>
1 parent 0cce109 commit e6a82e2

File tree

10 files changed

+552
-0
lines changed

10 files changed

+552
-0
lines changed

examples/stdlib/acme.effekt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import char
1111
import dequeue
1212
import effekt
1313
import exception
14+
import heap
1415
import io
1516
import io/console
1617
import io/error
@@ -25,6 +26,7 @@ import process
2526
import queue
2627
import ref
2728
import regex
29+
import resizable_array
2830
import result
2931
import scanner
3032
import seq
@@ -33,5 +35,6 @@ import stream
3335
import string
3436
import test
3537
import tty
38+
import union_find
3639

3740
def main() = ()

examples/stdlib/heap/heap.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
HeapTests
2+
✓ simple heap sort on integers
3+
4+
1 pass
5+
0 fail
6+
1 tests total

examples/stdlib/heap/heap.effekt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import heap
2+
import test
3+
4+
def main() = {
5+
suite("HeapTests", false) {
6+
test("simple heap sort on integers") {
7+
with on[OutOfBounds].default { assertTrue(false); <> };
8+
val h = heap[Int](box { (x: Int, y: Int) =>
9+
if (x < y) {
10+
Less()
11+
} else Greater() // hacky, should sometimes be Equal()
12+
})
13+
h.insert(12)
14+
h.insert(10)
15+
h.insert(7)
16+
h.insert(11)
17+
h.insert(14)
18+
assert(h.deleteMin(), 7)
19+
assert(h.deleteMin(), 10)
20+
assert(h.deleteMin(), 11)
21+
assert(h.deleteMin(), 12)
22+
assert(h.deleteMin(), 14)
23+
assert(h.size, 0)
24+
}
25+
};
26+
()
27+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ResizableArrayTests
2+
✓ usage as stack
3+
4+
1 pass
5+
0 fail
6+
1 tests total
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import resizable_array
2+
import test
3+
4+
def main() = {
5+
suite("ResizableArrayTests", false) {
6+
test("usage as stack") {
7+
with on[OutOfBounds].default { assertTrue(false, "out of bounds") }
8+
val a = resizableArray()
9+
a.add(1)
10+
a.add(1)
11+
a.add(2)
12+
a.add(3)
13+
a.add(13)
14+
a.add(21)
15+
a.add(34)
16+
a.add(55)
17+
assert(a.popRight(), 55)
18+
assert(a.popRight(), 34)
19+
assert(a.popRight(), 21)
20+
assert(a.popRight(), 13)
21+
a.add(5)
22+
a.add(8)
23+
assert(a.popRight(), 8)
24+
assert(a.popRight(), 5)
25+
assert(a.popRight(), 3)
26+
assert(a.popRight(), 2)
27+
assert(a.popRight(), 1)
28+
assert(a.popRight(), 1)
29+
}
30+
};
31+
()
32+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
UnionFindTests
2+
✓ three elements
3+
4+
1 pass
5+
0 fail
6+
1 tests total
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import union_find
2+
import test
3+
4+
def main() = {
5+
suite("UnionFindTests", false) {
6+
test("three elements") {
7+
with on[MissingValue].default { assertTrue(false, "MissingValue exception") }
8+
val u = unionFind()
9+
val a = u.makeSet()
10+
val b = u.makeSet()
11+
val c = u.makeSet()
12+
assert(u.find(a), a)
13+
assert(u.find(b), b)
14+
assert(u.find(c), c)
15+
u.union(a,b)
16+
assert(u.find(a), u.find(b))
17+
assert(u.find(c), c)
18+
}
19+
};
20+
()
21+
}

libraries/common/heap.effekt

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
module heap
2+
import resizable_array
3+
4+
/// Resizable 2-ary min-heap, backed by a resizable array
5+
/// `cmp` defines the ordering of elements
6+
record Heap[T](rawContents: ResizableArray[T], cmp: (T, T) => Ordering at {})
7+
8+
/// Make a new Heap with the given comparison operation
9+
def heap[T](cmp: (T,T) => Ordering at {}) =
10+
Heap[T](resizableArray(), cmp)
11+
12+
/// Make a new Heap with the given comparison operation and initial capacity
13+
def heap[T](cmp: (T,T) => Ordering at {}, capacity: Int) =
14+
Heap[T](resizableArray(capacity), cmp)
15+
16+
namespace internal {
17+
def left(idx: Int) = 2 * idx + 1
18+
def right(idx: Int) = 2 * idx + 2
19+
def parent(idx: Int) = (idx - 1) / 2
20+
21+
def bubbleUp[A](heap: Heap[A], idx: Int) = {
22+
val arr = heap.rawContents
23+
arr.boundsCheck(idx) // idx > parent(idx), parent(parent(idx)) etc
24+
25+
def go(idx: Int): Unit = {
26+
if (idx > 0 and (heap.cmp)(arr.unsafeGet(parent(idx)), arr.unsafeGet(idx)) is Greater()) {
27+
arr.unsafeSwap(parent(idx), idx)
28+
go(parent(idx))
29+
}
30+
}
31+
go(idx)
32+
}
33+
def sinkDown[A](heap: Heap[A], idx: Int) = {
34+
val arr = heap.rawContents
35+
36+
def infixLt(x: A, y: A) = {
37+
if ((heap.cmp)(x,y) is Less()) { true } else { false }
38+
}
39+
40+
def go(idx: Int): Unit = {
41+
if (right(idx) < arr.size) {
42+
val v = arr.unsafeGet(idx)
43+
val l = arr.unsafeGet(left(idx))
44+
val r = arr.unsafeGet(right(idx))
45+
if (l < v && r < v) {
46+
// swap with the smaller one
47+
if (l < r) {
48+
arr.unsafeSwap(left(idx), idx)
49+
go(left(idx))
50+
} else {
51+
arr.unsafeSwap(right(idx), idx)
52+
go(right(idx))
53+
}
54+
} else if (l < v) {
55+
arr.unsafeSwap(left(idx), idx)
56+
go(left(idx))
57+
} else if (r < v) {
58+
arr.unsafeSwap(right(idx), idx)
59+
go(right(idx))
60+
}
61+
} else if (left(idx) < arr.size) {
62+
if (arr.unsafeGet(left(idx)) < arr.unsafeGet(idx)) {
63+
arr.unsafeSwap(left(idx), idx)
64+
go(left(idx))
65+
}
66+
} // else: we are at the bottom
67+
}
68+
go(idx)
69+
}
70+
}
71+
72+
/// Insert value into heap
73+
///
74+
/// O(log n) worst case if capacity suffices, O(1) average
75+
def insert[T](heap: Heap[T], value: T): Unit = {
76+
with on[OutOfBounds].panic();
77+
val idx = heap.rawContents.add(value)
78+
internal::bubbleUp(heap, idx)
79+
}
80+
81+
/// find and return (but not remove) the minimal element in this heap
82+
///
83+
/// O(1)
84+
def findMin[T](heap: Heap[T]): T / Exception[OutOfBounds] = {
85+
heap.rawContents.get(0)
86+
}
87+
88+
/// find and remove the minimal element in this heap
89+
///
90+
/// O(log n)
91+
def deleteMin[T](heap: Heap[T]): T / Exception[OutOfBounds] = {
92+
val res = heap.rawContents.get(0)
93+
heap.rawContents.unsafeSet(0, heap.rawContents.popRight())
94+
internal::sinkDown(heap, 0)
95+
res
96+
}
97+
98+
/// Number of elements in the heap
99+
///
100+
/// O(1)
101+
def size[T](heap: Heap[T]): Int = {
102+
heap.rawContents.size
103+
}

0 commit comments

Comments
 (0)