From 0995e6db90b4eca57b30335c269677623e6c312e Mon Sep 17 00:00:00 2001 From: Michael Bradley Date: Wed, 8 Jan 2025 22:52:25 +1300 Subject: [PATCH] Get basic incomplete Python queue API working --- Cargo.toml | 2 +- src/backing/item.rs | 21 +++++++++------ src/backing/keyed/mod.rs | 4 ++- src/backing/pure/binary_heap.rs | 45 +++++++++++++++------------------ src/backing/pure/mod.rs | 4 +-- src/lib.rs | 12 +++------ src/queue/mod.rs | 1 + src/queue/pure.rs | 25 ++++++++++++++++++ 8 files changed, 69 insertions(+), 45 deletions(-) create mode 100644 src/queue/mod.rs create mode 100644 src/queue/pure.rs diff --git a/Cargo.toml b/Cargo.toml index 0da9dff..db86304 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,4 @@ name = "pyority_queue" crate-type = ["cdylib", "rlib"] [dependencies] -pyo3 = "0.23.3" +pyo3 = { version = "0.23.3", features = ["py-clone"] } diff --git a/src/backing/item.rs b/src/backing/item.rs index 424bf45..55ac901 100644 --- a/src/backing/item.rs +++ b/src/backing/item.rs @@ -2,12 +2,13 @@ use std::cmp::Ordering; /// Helper struct to associate an item with its priority #[derive(Debug, Clone, Copy)] -pub struct Item { +// I mean I guess P should be Ord but I want to use f64 so whatever +pub struct Item { data: D, priority: P, } -impl Item { +impl Item { /// Creates a new instance fn new(data: D, priority: P) -> Self { Self { data, priority } @@ -20,22 +21,26 @@ impl Item { } // The relevant Ord implementations are based just on the priority -impl Ord for Item { +impl Ord for Item { fn cmp(&self, other: &Self) -> Ordering { - self.priority.cmp(&other.priority) + // Yeah this is bad design + // My excuse is that i'm still learning Rust + self.priority + .partial_cmp(&other.priority) + .unwrap_or(Ordering::Equal) } } -impl PartialOrd for Item { +impl PartialOrd for Item { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) + self.priority.partial_cmp(&other.priority) } } -impl PartialEq for Item { +impl PartialEq for Item { fn eq(&self, other: &Self) -> bool { self.priority == other.priority } } -impl Eq for Item {} +impl Eq for Item {} diff --git a/src/backing/keyed/mod.rs b/src/backing/keyed/mod.rs index 58077c5..e9bc311 100644 --- a/src/backing/keyed/mod.rs +++ b/src/backing/keyed/mod.rs @@ -2,7 +2,9 @@ use super::{item::Item, pure::PureBacking}; /// A data structure usable for backing a "keyed" queue -pub trait KeyedBacking: PureBacking> { +pub trait KeyedBacking: + PureBacking> +{ /// Update an item's priority fn update(data: D, priority: P) -> Result<(), ()>; /// Remove an item from the queue diff --git a/src/backing/pure/binary_heap.rs b/src/backing/pure/binary_heap.rs index 4d79d97..6569a66 100644 --- a/src/backing/pure/binary_heap.rs +++ b/src/backing/pure/binary_heap.rs @@ -4,7 +4,7 @@ use super::PureBacking; /// A binary min-heap backed by an array #[derive(Debug)] -pub struct BinaryHeap { +pub struct BinaryHeap { data: Vec, } @@ -33,18 +33,23 @@ impl fmt::Display for SiftError { /// Whether a sift operation succeeded type SiftResult = Result<(), SiftError>; -impl BinaryHeap { +impl BinaryHeap { + /// Instantiates a new (empty) binary heap + pub fn new() -> Self { + Self { data: vec![] } + } + /// Fix an index representing a node with valid children but that may violate the heap property compared to its immediate parent fn sift_up(&mut self, i: usize) -> SiftResult { if i == 0 { // Base case, at root so nothing to do Ok(()) - } else if let Some(child) = self.data.get(i).copied() { + } else if let Some(child) = self.data.get(i).cloned() { let parent_index = (i - 1) / 2; // Check if the heap property is violated if child < self.data[parent_index] { // Swap child with parent - self.data[i] = self.data[parent_index]; + self.data[i] = self.data[parent_index].clone(); self.data[parent_index] = child; // Repeat process with parent @@ -64,12 +69,12 @@ impl BinaryHeap { // Tried to sift a non-existent index Err(SiftError::new(i, self.data.len())) } else { - if let Some(first_child) = self.data.get(i * 2 + 1).copied() { + if let Some(first_child) = self.data.get(i * 2 + 1).cloned() { let smaller_child_index; let smaller_child; // Find the smallest child and its index - if let Some(second_child) = self.data.get(i * 2 + 2).copied() { + if let Some(second_child) = self.data.get(i * 2 + 2).cloned() { // Both children, use the smaller one if first_child < second_child { smaller_child = first_child; @@ -86,7 +91,7 @@ impl BinaryHeap { if smaller_child < self.data[i] { // Swap parent with child - self.data[smaller_child_index] = self.data[i]; + self.data[smaller_child_index] = self.data[i].clone(); self.data[i] = smaller_child; // Repeat process with child @@ -103,23 +108,19 @@ impl BinaryHeap { } } -impl FromIterator for BinaryHeap { +impl FromIterator for BinaryHeap { fn from_iter>(iter: U) -> Self { let mut this = Self { data: Vec::from_iter(iter), }; for i in (0..=(this.data.len() / 2)).rev() { - this.sift_down(i); + this.sift_down(i).expect("Index error during heapify"); } this } } -impl PureBacking for BinaryHeap { - fn new() -> Self { - Self { data: vec![] } - } - +impl PureBacking for BinaryHeap { fn add(&mut self, item: T) { // Append item self.data.push(item); @@ -132,22 +133,18 @@ impl PureBacking for BinaryHeap { // No extra processing 0 | 1 => self.data.pop(), _ => { - let last = self + // Get the original root + let root = self.data[0].clone(); + + // Move final item to the root and sift down to regain heap property + self.data[0] = self .data .pop() .expect("Vector claimed not to be empty but was"); - let root = self - .data - .get_mut(0) - .expect("Vector claimed to have multiple items but didn't"); - - // Move final item to the root and sift down to regain heap property - let best = *root; - *root = last; self.sift_down(0).unwrap(); // Return original root - Some(best) + Some(root) } } } diff --git a/src/backing/pure/mod.rs b/src/backing/pure/mod.rs index 9d82347..9c43b14 100644 --- a/src/backing/pure/mod.rs +++ b/src/backing/pure/mod.rs @@ -2,9 +2,7 @@ pub mod binary_heap; /// A data structure usable for backing a "pure" queue -pub trait PureBacking: FromIterator { - /// Instantiates a new data structure - fn new() -> Self; +pub trait PureBacking: Send + Sync { /// Places an item into the queue fn add(&mut self, item: T); /// Removes the item with minimum priority, if it exists diff --git a/src/lib.rs b/src/lib.rs index c2bf43e..64be5b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,12 @@ pub mod backing; +pub mod queue; use pyo3::prelude::*; +use queue::pure::PureQueue; -/// Formats the sum of two numbers as string. -#[pyfunction] -fn sum_as_string(a: usize, b: usize) -> PyResult { - Ok((a + b).to_string()) -} - -/// A Python module implemented in Rust. +/// Bindings for the Rust queue implementations #[pymodule] fn pyority_queue(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_class::()?; Ok(()) } diff --git a/src/queue/mod.rs b/src/queue/mod.rs new file mode 100644 index 0000000..9ec004d --- /dev/null +++ b/src/queue/mod.rs @@ -0,0 +1 @@ +pub mod pure; diff --git a/src/queue/pure.rs b/src/queue/pure.rs new file mode 100644 index 0000000..4c03a26 --- /dev/null +++ b/src/queue/pure.rs @@ -0,0 +1,25 @@ +// A "pure" priority queue that supports duplicates, but not arbitrary deletions or weight updates +use crate::backing::{ + item::Item, + pure::{binary_heap::BinaryHeap, PureBacking}, +}; +use pyo3::prelude::*; + +#[pyclass] +pub struct PureQueue { + backing: Box, f64>>>, +} + +#[pymethods] +impl PureQueue { + #[new] + fn new() -> Self { + Self { + backing: Box::new(BinaryHeap::new()), + } + } + + fn __len__(self_: PyRef<'_, Self>) -> usize { + self_.backing.len() + } +}