use std::cmp::Ordering;
use std::collections::BinaryHeap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Category {
Primary,
Test,
Caller,
Callee,
Config,
Doc,
}
impl Category {
pub fn weight(&self) -> f64 {
match self {
Category::Primary => 1.0,
Category::Test => 0.8,
Category::Caller => 0.6,
Category::Callee => 0.6,
Category::Doc => 0.4,
Category::Config => 0.3,
}
}
}
#[derive(Debug, Clone)]
pub struct PriorityItem<T> {
priority: f64,
category: Category,
sequence: usize,
item: T,
}
impl<T> PriorityItem<T> {
pub fn new(priority: f64, category: Category, sequence: usize, item: T) -> Self {
Self {
priority,
category,
sequence,
item,
}
}
pub fn priority(&self) -> f64 {
self.priority
}
pub fn category(&self) -> Category {
self.category
}
pub fn sequence(&self) -> usize {
self.sequence
}
pub fn item(&self) -> &T {
&self.item
}
pub fn into_inner(self) -> T {
self.item
}
}
impl<T> Ord for PriorityItem<T> {
fn cmp(&self, other: &Self) -> Ordering {
match self.priority.partial_cmp(&other.priority) {
Some(Ordering::Equal) | None => {
other.sequence.cmp(&self.sequence)
}
Some(ord) => ord,
}
}
}
impl<T> PartialOrd for PriorityItem<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> PartialEq for PriorityItem<T> {
fn eq(&self, other: &Self) -> bool {
self.priority == other.priority && self.sequence == other.sequence
}
}
impl<T> Eq for PriorityItem<T> {}
#[derive(Debug, Clone)]
pub struct PriorityQueue<T> {
heap: BinaryHeap<PriorityItem<T>>,
sequence: usize,
}
impl<T> PriorityQueue<T> {
pub fn new() -> Self {
Self {
heap: BinaryHeap::new(),
sequence: 0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
heap: BinaryHeap::with_capacity(capacity),
sequence: 0,
}
}
pub fn push(&mut self, base_priority: f64, category: Category, item: T) {
let priority = base_priority * category.weight();
let priority_item = PriorityItem::new(priority, category, self.sequence, item);
self.heap.push(priority_item);
self.sequence += 1;
}
pub fn pop(&mut self) -> Option<PriorityItem<T>> {
self.heap.pop()
}
pub fn peek(&self) -> Option<&PriorityItem<T>> {
self.heap.peek()
}
pub fn len(&self) -> usize {
self.heap.len()
}
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}
pub fn clear(&mut self) {
self.heap.clear();
self.sequence = 0;
}
pub fn drain(&mut self) -> Drain<'_, T> {
Drain { queue: self }
}
}
impl<T> Default for PriorityQueue<T> {
fn default() -> Self {
Self::new()
}
}
pub struct Drain<'a, T> {
queue: &'a mut PriorityQueue<T>,
}
impl<'a, T> Iterator for Drain<'a, T> {
type Item = PriorityItem<T>;
fn next(&mut self) -> Option<Self::Item> {
self.queue.pop()
}
}
impl<'a, T> Drop for Drain<'a, T> {
fn drop(&mut self) {
while self.next().is_some() {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_category_weights() {
assert_eq!(Category::Primary.weight(), 1.0);
assert_eq!(Category::Test.weight(), 0.8);
assert_eq!(Category::Caller.weight(), 0.6);
assert_eq!(Category::Callee.weight(), 0.6);
assert_eq!(Category::Doc.weight(), 0.4);
assert_eq!(Category::Config.weight(), 0.3);
}
#[test]
fn test_category_weight_ordering() {
assert!(Category::Primary.weight() > Category::Test.weight());
assert!(Category::Test.weight() > Category::Caller.weight());
assert!(Category::Caller.weight() > Category::Doc.weight());
assert!(Category::Doc.weight() > Category::Config.weight());
}
#[test]
fn test_priority_item_creation() {
let item = PriorityItem::new(0.9, Category::Primary, 0, "data");
assert_eq!(item.priority(), 0.9);
assert_eq!(item.category(), Category::Primary);
assert_eq!(item.sequence(), 0);
assert_eq!(*item.item(), "data");
}
#[test]
fn test_priority_item_ordering() {
let high = PriorityItem::new(0.9, Category::Primary, 0, "high");
let low = PriorityItem::new(0.5, Category::Caller, 1, "low");
assert!(high > low);
assert!(low < high);
}
#[test]
fn test_priority_item_sequence_ordering() {
let first = PriorityItem::new(0.9, Category::Primary, 0, "first");
let second = PriorityItem::new(0.9, Category::Primary, 1, "second");
assert!(first > second); }
#[test]
fn test_new_queue() {
let queue: PriorityQueue<String> = PriorityQueue::new();
assert_eq!(queue.len(), 0);
assert!(queue.is_empty());
}
#[test]
fn test_with_capacity() {
let queue: PriorityQueue<String> = PriorityQueue::with_capacity(100);
assert_eq!(queue.len(), 0);
}
#[test]
fn test_push_and_pop() {
let mut queue = PriorityQueue::new();
queue.push(0.9, Category::Primary, "high");
queue.push(0.5, Category::Caller, "low");
queue.push(0.7, Category::Test, "med");
assert_eq!(queue.len(), 3);
let first = queue.pop().unwrap();
assert_eq!(*first.item(), "high");
assert_eq!(first.category(), Category::Primary);
let second = queue.pop().unwrap();
assert_eq!(*second.item(), "med");
let third = queue.pop().unwrap();
assert_eq!(*third.item(), "low");
assert!(queue.is_empty());
assert!(queue.pop().is_none());
}
#[test]
fn test_weighted_priority() {
let mut queue = PriorityQueue::new();
queue.push(1.0, Category::Config, "config"); queue.push(1.0, Category::Test, "test"); queue.push(1.0, Category::Primary, "primary");
assert_eq!(*queue.pop().unwrap().item(), "primary");
assert_eq!(*queue.pop().unwrap().item(), "test");
assert_eq!(*queue.pop().unwrap().item(), "config");
}
#[test]
fn test_peek() {
let mut queue = PriorityQueue::new();
queue.push(0.5, Category::Caller, "low");
queue.push(0.9, Category::Primary, "high");
let item = queue.peek().unwrap();
assert_eq!(*item.item(), "high");
assert_eq!(queue.len(), 2);
queue.pop();
let item = queue.peek().unwrap();
assert_eq!(*item.item(), "low");
}
#[test]
fn test_peek_empty() {
let queue: PriorityQueue<String> = PriorityQueue::new();
assert!(queue.peek().is_none());
}
#[test]
fn test_clear() {
let mut queue = PriorityQueue::new();
queue.push(0.9, Category::Primary, "data1");
queue.push(0.8, Category::Test, "data2");
assert_eq!(queue.len(), 2);
queue.clear();
assert!(queue.is_empty());
assert_eq!(queue.len(), 0);
}
#[test]
fn test_drain() {
let mut queue = PriorityQueue::new();
queue.push(0.5, Category::Caller, "low");
queue.push(0.9, Category::Primary, "high");
queue.push(0.7, Category::Test, "med");
let items: Vec<_> = queue.drain().map(|i| i.into_inner()).collect();
assert_eq!(items, vec!["high", "med", "low"]);
assert!(queue.is_empty());
}
#[test]
fn test_stable_ordering_same_priority() {
let mut queue = PriorityQueue::new();
queue.push(1.0, Category::Caller, "first");
queue.push(1.0, Category::Caller, "second");
queue.push(1.0, Category::Caller, "third");
assert_eq!(*queue.pop().unwrap().item(), "first");
assert_eq!(*queue.pop().unwrap().item(), "second");
assert_eq!(*queue.pop().unwrap().item(), "third");
}
#[test]
fn test_into_inner() {
let item = PriorityItem::new(0.9, Category::Primary, 0, String::from("owned"));
let inner = item.into_inner();
assert_eq!(inner, "owned");
}
#[test]
fn test_many_items() {
let mut queue = PriorityQueue::new();
for i in 0..1000 {
let priority = (i as f64) / 1000.0;
queue.push(priority, Category::Caller, i);
}
assert_eq!(queue.len(), 1000);
let mut prev_priority = f64::INFINITY;
while let Some(item) = queue.pop() {
assert!(item.priority() <= prev_priority);
prev_priority = item.priority();
}
assert!(queue.is_empty());
}
#[test]
fn test_mixed_categories() {
let mut queue = PriorityQueue::new();
queue.push(0.8, Category::Config, "config"); queue.push(0.6, Category::Primary, "primary"); queue.push(0.7, Category::Test, "test"); queue.push(0.9, Category::Caller, "caller");
assert_eq!(*queue.pop().unwrap().item(), "primary"); assert_eq!(*queue.pop().unwrap().item(), "test"); assert_eq!(*queue.pop().unwrap().item(), "caller"); assert_eq!(*queue.pop().unwrap().item(), "config"); }
}