Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions data_structures/priority_queue.jule
Original file line number Diff line number Diff line change
@@ -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
}
}
}
75 changes: 75 additions & 0 deletions data_structures/priority_queue_test.jule
Original file line number Diff line number Diff line change
@@ -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")
}