use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::marker::PhantomData;
pub type MaxPriorityQ<P, T> = PriorityQueue<P, T, Max>;
pub type MinPriorityQ<P, T> = PriorityQueue<P, T, Min>;
pub struct Max;
pub struct Min;
#[derive(Debug, Default)]
pub struct PriorityQueue<P, T, O = Max>
where
P: Ord,
{
heap: BinaryHeap<RankedItem<P, T, O>>,
seq: u64,
}
impl<P: Ord, T, O> PriorityQueue<P, T, O> {
#[must_use]
pub fn new() -> Self {
Self {
heap: BinaryHeap::new(),
seq: 0,
}
}
fn pop_inner(&mut self) -> Option<(T, P)>
where
O: QueueOrder,
{
self.heap
.pop()
.map(|RankedItem { item, priority, .. }| (item, priority))
}
fn push_inner(&mut self, item: T, priority: P)
where
O: QueueOrder,
{
self.seq += 1;
self.heap.push(RankedItem {
item,
priority,
seq: self.seq,
_order: PhantomData,
});
}
}
impl<P: Ord, T> PriorityQueue<P, T, Max> {
pub fn pop(&mut self) -> Option<(T, P)> {
self.pop_inner()
}
pub fn push(&mut self, item: T, priority: P) {
self.push_inner(item, priority);
}
}
impl<P: Ord, T> PriorityQueue<P, T, Min> {
pub fn pop(&mut self) -> Option<(T, P)> {
self.pop_inner()
}
pub fn push(&mut self, item: T, priority: P) {
self.push_inner(item, priority);
}
}
trait QueueOrder {
fn cmp<P: Ord>(lhs_priority: &P, lhs_seq: u64, rhs_priority: &P, rhs_seq: u64) -> Ordering;
}
impl QueueOrder for Max {
fn cmp<P: Ord>(lhs_priority: &P, lhs_seq: u64, rhs_priority: &P, rhs_seq: u64) -> Ordering {
lhs_priority.cmp(rhs_priority).then(rhs_seq.cmp(&lhs_seq))
}
}
impl QueueOrder for Min {
fn cmp<P: Ord>(lhs_priority: &P, lhs_seq: u64, rhs_priority: &P, rhs_seq: u64) -> Ordering {
lhs_priority
.cmp(rhs_priority)
.reverse()
.then(rhs_seq.cmp(&lhs_seq))
}
}
#[derive(Debug)]
struct RankedItem<P, T, O>
where
P: Ord,
{
item: T,
priority: P,
seq: u64,
_order: PhantomData<O>,
}
impl<P, T, O> Ord for RankedItem<P, T, O>
where
P: Ord,
O: QueueOrder,
{
fn cmp(&self, other: &Self) -> Ordering {
O::cmp(&self.priority, self.seq, &other.priority, other.seq)
}
}
impl<P, T, O> PartialOrd for RankedItem<P, T, O>
where
P: Ord,
O: QueueOrder,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<P, T, O> Eq for RankedItem<P, T, O>
where
P: Ord,
O: QueueOrder,
{
}
impl<P, T, O> PartialEq for RankedItem<P, T, O>
where
P: Ord,
O: QueueOrder,
{
fn eq(&self, other: &Self) -> bool {
self.priority == other.priority && self.seq == other.seq
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_push_pop_max() {
let mut q = MaxPriorityQ::new();
q.push('c', 3);
q.push('a', 1);
q.push('b', 2);
assert_eq!(q.pop(), Some(('c', 3)));
assert_eq!(q.pop(), Some(('b', 2)));
assert_eq!(q.pop(), Some(('a', 1)));
assert_eq!(q.pop(), None);
}
#[test]
fn test_push_pop_min() {
let mut q = MinPriorityQ::new();
q.push('c', 3);
q.push('a', 1);
q.push('b', 2);
assert_eq!(q.pop(), Some(('a', 1)));
assert_eq!(q.pop(), Some(('b', 2)));
assert_eq!(q.pop(), Some(('c', 3)));
assert_eq!(q.pop(), None);
}
#[test]
fn ties_returned_in_insertion_order() {
let mut q = MaxPriorityQ::new();
q.push('a', 3);
q.push('b', 3);
q.push('c', 3);
assert_eq!(q.pop(), Some(('a', 3)));
assert_eq!(q.pop(), Some(('b', 3)));
assert_eq!(q.pop(), Some(('c', 3)));
assert_eq!(q.pop(), None);
let mut q = MinPriorityQ::new();
q.push('a', 3);
q.push('b', 3);
q.push('c', 3);
assert_eq!(q.pop(), Some(('a', 3)));
assert_eq!(q.pop(), Some(('b', 3)));
assert_eq!(q.pop(), Some(('c', 3)));
assert_eq!(q.pop(), None);
}
#[test]
fn empty_queues_pop_none() {
let mut max_q: MaxPriorityQ<i32, char> = MaxPriorityQ::new();
let mut min_q: MinPriorityQ<i32, char> = MinPriorityQ::new();
assert_eq!(max_q.pop(), None);
assert_eq!(min_q.pop(), None);
}
#[test]
fn explicit_generic_construction_respects_order_marker() {
let mut max_q = PriorityQueue::<_, _, Max>::new();
max_q.push("mid", 2);
max_q.push("high", 3);
max_q.push("low", 1);
assert_eq!(max_q.pop(), Some(("high", 3)));
assert_eq!(max_q.pop(), Some(("mid", 2)));
assert_eq!(max_q.pop(), Some(("low", 1)));
let mut min_q = PriorityQueue::<_, _, Min>::new();
min_q.push("mid", 2);
min_q.push("high", 3);
min_q.push("low", 1);
assert_eq!(min_q.pop(), Some(("low", 1)));
assert_eq!(min_q.pop(), Some(("mid", 2)));
assert_eq!(min_q.pop(), Some(("high", 3)));
}
}