diff --git a/data_structures/priority_queue.jule b/data_structures/priority_queue.jule new file mode 100644 index 0000000..031e51e --- /dev/null +++ b/data_structures/priority_queue.jule @@ -0,0 +1,94 @@ +// Implementation of a generic priority queue backed by a binary min-heap. +// Reference: https://en.wikipedia.org/wiki/Priority_queue + +// PriorityQueue is a generic priority queue. Items are ordered by their +// natural ordering, and the smallest item is always at the front of the +// queue (min-heap). +struct PriorityQueue[T: ordered] { + items: []T +} + +impl PriorityQueue { + // Push adds an item to the queue. + fn Push(mut *self, mut item: T) { + self.items = append(self.items, item) + self.siftUp(len(self.items) - 1) + } + + // Pop removes and returns the smallest item in the queue. + // The second return value reports whether an item was removed; + // it is false when the queue is empty. + fn Pop(mut *self): (item: T, ok: bool) { + if len(self.items) == 0 { + return + } + item = self.items[0] + ok = true + + last := len(self.items) - 1 + self.items[0] = self.items[last] + self.items = self.items[:last] + if len(self.items) > 0 { + self.siftDown(0) + } + return + } + + // Peek returns the smallest item in the queue without removing it. + // The second return value reports whether the queue has an item; + // it is false when the queue is empty. + fn Peek(mut *self): (item: T, ok: bool) { + if len(self.items) == 0 { + return + } + item = self.items[0] + ok = true + return + } + + // Len returns the number of items in the queue. + fn Len(*self): int { + return len(self.items) + } + + // Empty reports whether the queue has no items. + fn Empty(*self): bool { + return len(self.items) == 0 + } + + // siftUp restores the heap property by moving the item at index i + // up towards the root while it is smaller than its parent. + fn siftUp(mut *self, mut i: int) { + for i > 0 { + parent := (i - 1) / 2 + if self.items[i] < self.items[parent] { + self.items[i], self.items[parent] = self.items[parent], self.items[i] + i = parent + } else { + break + } + } + } + + // siftDown restores the heap property by moving the item at index i + // down towards the leaves while it is bigger than one of its children. + fn siftDown(mut *self, mut i: int) { + n := len(self.items) + for { + left := 2*i + 1 + right := 2*i + 2 + mut smallest := i + if left < n && self.items[left] < self.items[smallest] { + smallest = left + } + if right < n && self.items[right] < self.items[smallest] { + smallest = right + } + if smallest == i { + break + } + self.items[i], self.items[smallest] = self.items[smallest], self.items[i] + i = smallest + } + } +} diff --git a/data_structures/priority_queue_test.jule b/data_structures/priority_queue_test.jule new file mode 100644 index 0000000..6904f83 --- /dev/null +++ b/data_structures/priority_queue_test.jule @@ -0,0 +1,75 @@ +#build test + +use "std/slices" +use "std/testing" + +#test +fn testPriorityQueuePushPop(t: &testing::T) { + mut pq := PriorityQueue[int]{} + t.Assert(pq.Empty(), "new priority queue should be empty") + t.Assert(pq.Len() == 0, "new priority queue should have length 0") + + pq.Push(5) + pq.Push(1) + pq.Push(3) + pq.Push(2) + pq.Push(4) + t.Assert(!pq.Empty(), "priority queue with items should not be empty") + t.Assert(pq.Len() == 5, "priority queue should have length 5") + + mut got := make([]int, 0, 5) + for !pq.Empty() { + v, ok := pq.Pop() + t.Assert(ok, "pop should succeed while queue is not empty") + got = append(got, v) + } + t.Assert(slices::Equal(got, [1, 2, 3, 4, 5]), "pop should always return the smallest remaining item") + t.Assert(pq.Empty(), "priority queue should be empty after popping all items") +} + +#test +fn testPriorityQueuePopEmpty(t: &testing::T) { + mut pq := PriorityQueue[int]{} + _, ok := pq.Pop() + t.Assert(!ok, "pop on empty priority queue should report ok == false") +} + +#test +fn testPriorityQueuePeek(t: &testing::T) { + mut pq := PriorityQueue[int]{} + _, ok := pq.Peek() + t.Assert(!ok, "peek on empty priority queue should report ok == false") + + pq.Push(10) + pq.Push(3) + pq.Push(7) + v, ok2 := pq.Peek() + t.Assert(ok2 && v == 3, "peek should return the smallest item (3)") + t.Assert(pq.Len() == 3, "peek should not remove the item") +} + +#test +fn testPriorityQueueDuplicates(t: &testing::T) { + mut pq := PriorityQueue[int]{} + pq.Push(2) + pq.Push(2) + pq.Push(1) + pq.Push(1) + + mut got := make([]int, 0, 4) + for !pq.Empty() { + v, _ := pq.Pop() + got = append(got, v) + } + t.Assert(slices::Equal(got, [1, 1, 2, 2]), "duplicate items should still pop in sorted order") +} + +#test +fn testPriorityQueueGenericString(t: &testing::T) { + mut pq := PriorityQueue[string]{} + pq.Push("banana") + pq.Push("apple") + pq.Push("cherry") + v, ok := pq.Pop() + t.Assert(ok && v == "apple", "generic string priority queue should pop \"apple\" first") +}