Packages

A priority queue implementation based on max pairing heaps. Written in pure Gleam.

Current section

Files

Jump to
priorityq src priorityq.gleam
Raw

src/priorityq.gleam

import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/order.{type Order, Gt}
/// A priority queue implemented with a max pairing heap.
///
pub opaque type PriorityQueue(t) {
PriorityQueue(data: PairingHeap(t), cmp: Cmp(t))
}
type PairingHeap(t) {
Empty
NonEmpty(PairingTree(t))
}
type Cmp(t) =
fn(t, t) -> Order
type PairingTree(t) {
PairingTree(val: t, children: List(PairingTree(t)), size: Int)
}
/// Creates an empty priority queue.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// new(int.compare) // -> PriorityQueue(Int)
/// ```
///
pub fn new(cmp: Cmp(t)) -> PriorityQueue(t) {
PriorityQueue(Empty, cmp)
}
/// Creates a priority queue from a list.
///
/// Runs in linear time.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// from_list([1, 10, 5], int.compare) // -> PriorityQueue(Int)
/// ```
///
pub fn from_list(ls: List(t), cmp: Cmp(t)) -> PriorityQueue(t) {
new(cmp)
|> list.fold(ls, _, push)
}
fn from_pairing_tree(tree: PairingTree(t), cmp: Cmp(t)) -> PriorityQueue(t) {
PriorityQueue(NonEmpty(tree), cmp)
}
fn one(val: t, cmp: Cmp(t)) -> PriorityQueue(t) {
PriorityQueue(NonEmpty(PairingTree(val, [], size: 1)), cmp)
}
/// Returns whether the priority queue is empty.
///
/// Runs in constant time.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// new(int.compare) |> is_empty() // -> True
/// from_list([0], int.compare) |> is_empty() // -> False
/// ```
///
pub fn is_empty(pq: PriorityQueue(t)) -> Bool {
pq.data == Empty
}
/// Returns the number of elements in the priority queue.
///
/// Runs in constant time.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// from_list([1, 2, 3], int.compare) |> size() // -> 3
/// ```
///
pub fn size(pq: PriorityQueue(t)) -> Int {
case pq.data {
Empty -> 0
NonEmpty(tree) -> tree.size
}
}
/// Returns the maximum value in the priority queue.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// new(int.compare) |> peek() // -> None
/// from_list([1, 10, 5], int.compare) |> peek() // -> 10
/// ```
///
pub fn peek(pq: PriorityQueue(t)) -> Option(t) {
case pq.data {
Empty -> None
NonEmpty(tree) -> Some(tree.val)
}
}
fn merge(pq1: PriorityQueue(t), pq2: PriorityQueue(t)) -> PriorityQueue(t) {
case pq1.cmp == pq2.cmp {
False -> panic as "inconsistent cmp function"
True ->
case pq1.data, pq2.data {
Empty, _ -> pq2
_, Empty -> pq1
NonEmpty(tree1), NonEmpty(tree2) -> {
let new_size = tree1.size + tree2.size
case pq1.cmp(tree1.val, tree2.val) {
Gt ->
PriorityQueue(
NonEmpty(PairingTree(
tree1.val,
[tree2, ..tree1.children],
new_size,
)),
pq1.cmp,
)
_ ->
PriorityQueue(
NonEmpty(PairingTree(
tree2.val,
[tree1, ..tree2.children],
new_size,
)),
pq1.cmp,
)
}
}
}
}
}
/// Pushes a value into the priority queue.
///
/// Runs in constant time.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// new(int.compare) |> push(10) // -> PriorityQueue(Int)
/// ```
///
pub fn push(pq: PriorityQueue(t), val: t) -> PriorityQueue(t) {
merge(one(val, pq.cmp), pq)
}
/// Pops the maximum value from the priority queue.
///
/// Runs in amortized logarithmic time.
///
/// ## Examples
///
/// ```gleam
/// import gleam/int
///
/// from_list([0]) |> pop() // -> PriorityQueue(Int)
/// ```
///
pub fn pop(pq: PriorityQueue(t)) -> PriorityQueue(t) {
case pq.data {
Empty -> pq
NonEmpty(tree) -> merge_pairs(tree.children, pq.cmp)
}
}
fn merge_pairs(trees: List(PairingTree(t)), cmp: Cmp(t)) -> PriorityQueue(t) {
case trees {
[] -> PriorityQueue(Empty, cmp)
[tree] -> from_pairing_tree(tree, cmp)
[tree1, tree2, ..rest] ->
merge(from_pairing_tree(tree1, cmp), from_pairing_tree(tree2, cmp))
|> merge(merge_pairs(rest, cmp))
}
}