diff --git a/src/backing/containers/mod.rs b/src/backing/containers/mod.rs deleted file mode 100644 index 74cfd32..0000000 --- a/src/backing/containers/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod pair; -mod py_item; - -pub use pair::Pair; -pub use py_item::PyItem; diff --git a/src/backing/containers/pair.rs b/src/backing/containers/pair.rs deleted file mode 100644 index d194835..0000000 --- a/src/backing/containers/pair.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::cmp::Ordering; - -/// Container to associate an item with a priority -#[derive(Debug, Clone, Copy)] -pub struct Pair { - data: D, - priority: P, -} - -impl Pair { - /// Creates a new instance - pub fn new(data: D, priority: P) -> Self { - Self { data, priority } - } - - /// Retrieves the internal data. - /// It would be nicer to implement this using [`From`] or [`Into`], but I don't see a way to do that using generics. - pub fn data(self) -> D { - self.data - } -} - -impl PartialOrd for Pair { - fn partial_cmp(&self, other: &Self) -> Option { - self.priority.partial_cmp(&other.priority) - } -} - -impl PartialEq for Pair { - fn eq(&self, other: &Self) -> bool { - self.priority == other.priority - } -} diff --git a/src/backing/containers/py_item.rs b/src/backing/containers/py_item.rs deleted file mode 100644 index 81c38af..0000000 --- a/src/backing/containers/py_item.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::cmp::Ordering; - -use pyo3::prelude::*; - -/// Container that provides PartialOrd based on the Python object it holds -#[derive(Debug, Clone)] -pub struct PyItem(Py); - -impl PyItem { - /// Creates a new instance - pub fn new(item: Py) -> Self { - PyItem(item) - } - - /// Retrieves the internal data - pub fn data(self) -> Py { - self.0 - } -} - -impl PartialOrd for PyItem { - fn partial_cmp(&self, other: &Self) -> Option { - Python::with_gil(|py| { - // Bind objects for convenience - let ours = self.0.bind(py); - let theirs = other.0.bind(py); - - // Compare based on Python comparison implementations - match ours.lt(theirs) { - Ok(true) => Some(Ordering::Less), - Ok(false) => match ours.gt(theirs) { - Ok(true) => Some(Ordering::Greater), - Ok(false) => Some(Ordering::Equal), // If the python class is implemented strangely then this may be wrong - Err(_) => None, - }, - Err(_) => None, - } - }) - } -} - -impl PartialEq for PyItem { - fn eq(&self, other: &Self) -> bool { - Python::with_gil(|py| self.0.bind(py).eq(other.0.bind(py)).unwrap_or(false)) - } -} diff --git a/src/backing/indexed/mod.rs b/src/backing/indexed/mod.rs index 6579634..ebc1ad5 100644 --- a/src/backing/indexed/mod.rs +++ b/src/backing/indexed/mod.rs @@ -1,9 +1,9 @@ /// Data structures for the "indexed" min-queues, supporting priority updates and arbitrary removals, but no duplicates -use super::{containers::Pair, pure::PureBacking}; +use super::{item::Item, pure::PureBacking}; /// A data structure usable for backing an "indexed" queue -pub trait IndexedBacking: - PureBacking> +pub trait IndexedBacking: + PureBacking> { /// Update an item's priority fn update(data: D, priority: P) -> Result<(), ()>; diff --git a/src/backing/item.rs b/src/backing/item.rs new file mode 100644 index 0000000..8c926a5 --- /dev/null +++ b/src/backing/item.rs @@ -0,0 +1,46 @@ +use std::cmp::Ordering; + +/// Helper struct to associate an item with its priority +#[derive(Debug, Clone, Copy)] +// 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 { + /// Creates a new instance + pub fn new(data: D, priority: P) -> Self { + Self { data, priority } + } + + /// Retrieve the internal data, it would be nicer to implement this using [`From`] or [`Into`], but I don't see a way to do that using generics + pub fn data(self) -> D { + self.data + } +} + +// The relevant Ord implementations are based just on the priority +impl Ord for Item { + fn cmp(&self, other: &Self) -> Ordering { + // 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 { + fn partial_cmp(&self, other: &Self) -> Option { + self.priority.partial_cmp(&other.priority) + } +} + +impl PartialEq for Item { + fn eq(&self, other: &Self) -> bool { + self.priority == other.priority + } +} + +impl Eq for Item {} diff --git a/src/backing/mod.rs b/src/backing/mod.rs index 65d0cb1..1c34cd3 100644 --- a/src/backing/mod.rs +++ b/src/backing/mod.rs @@ -1,3 +1,3 @@ -pub mod containers; pub mod indexed; +pub mod item; pub mod pure; diff --git a/src/backing/pure/binary_heap.rs b/src/backing/pure/binary_heap.rs index cd84f18..6569a66 100644 --- a/src/backing/pure/binary_heap.rs +++ b/src/backing/pure/binary_heap.rs @@ -2,6 +2,12 @@ use std::fmt; use super::PureBacking; +/// A binary min-heap backed by an array +#[derive(Debug)] +pub struct BinaryHeap { + data: Vec, +} + /// Indicates why a sift failed #[derive(Debug, Clone)] struct SiftError { @@ -27,13 +33,7 @@ impl fmt::Display for SiftError { /// Whether a sift operation succeeded type SiftResult = Result<(), SiftError>; -/// A binary min-heap backed by an array -#[derive(Debug)] -pub struct BinaryHeap { - data: Vec, -} - -impl BinaryHeap { +impl BinaryHeap { /// Instantiates a new (empty) binary heap pub fn new() -> Self { Self { data: vec![] } @@ -108,7 +108,7 @@ 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), @@ -120,7 +120,7 @@ impl FromIterator for BinaryHeap { } } -impl PureBacking for BinaryHeap { +impl PureBacking for BinaryHeap { fn add(&mut self, item: T) { // Append item self.data.push(item); diff --git a/src/backing/pure/mod.rs b/src/backing/pure/mod.rs index 1ff9d9a..a8b0259 100644 --- a/src/backing/pure/mod.rs +++ b/src/backing/pure/mod.rs @@ -3,7 +3,7 @@ mod binary_heap; pub use binary_heap::BinaryHeap; /// A data structure usable for backing a "pure" queue -pub trait PureBacking: Send + Sync { +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/queue/paired.rs b/src/queue/paired.rs index 5bb5f06..84f743f 100644 --- a/src/queue/paired.rs +++ b/src/queue/paired.rs @@ -1,5 +1,6 @@ +/// A "paired" priority queue that links some data to a priority and supports duplicates, but not arbitrary deletions or weight updates use crate::backing::{ - containers::Pair, + item::Item, pure::{BinaryHeap, PureBacking}, }; use pyo3::{ @@ -10,13 +11,11 @@ use pyo3::{ #[pyclass] pub struct PairedQueue { - backing: Box, f64>>>, + backing: Box, f64>>>, } -/// A "paired" priority queue that links some data to a priority and supports duplicates, but not arbitrary deletions or priority updates #[pymethods] impl PairedQueue { - /// Enables generic typing #[classmethod] fn __class_getitem__(cls_: Bound<'_, PyType>, _key: Py) -> Bound<'_, PyType> { cls_ @@ -63,13 +62,12 @@ impl PairedQueue { } fn __setitem__(mut self_: PyRefMut<'_, Self>, key: Py, value: f64) { - self_.backing.add(Pair::new(key, value)); + self_.backing.add(Item::new(key, value)); } } impl<'py> PairedQueue { - /// Tries to a Python object into a vector suitable for ingestion into the backing - fn from_any(object: &Bound<'py, PyAny>) -> PyResult, f64>>> { + fn from_any(object: &Bound<'py, PyAny>) -> PyResult, f64>>> { if let Ok(vec) = object.extract::, f64)>>() { Ok(Self::from_vec(vec)) } else { @@ -89,26 +87,24 @@ impl<'py> PairedQueue { } } - /// Converts a vector of Python objects and priorities into a vector of items - fn from_vec(list: Vec<(Py, f64)>) -> Vec, f64>> { + fn from_vec(list: Vec<(Py, f64)>) -> Vec, f64>> { list.into_iter() - .map(|(data, priority)| Pair::new(data, priority)) + .map(|(data, priority)| Item::new(data, priority)) .collect() } - /// Converts a Python dictionary into a vector of items - fn from_dict(dict: &Bound<'py, PyDict>) -> PyResult, f64>>> { + fn from_dict(dict: &Bound<'py, PyDict>) -> PyResult, f64>>> { if let Ok(items) = dict .into_iter() .map(|(data, priority)| match priority.extract::() { - Ok(value) => Ok(Pair::new(data.unbind(), value)), + Ok(value) => Ok(Item::new(data.unbind(), value)), Err(err) => Err(err), }) .collect::, _>>() { Ok(items) } else { - Err(PyErr::new::("Dict keys were not numbers")) + Err(PyErr::new::("Dict keys were not floats")) } } } diff --git a/src/queue/pure.rs b/src/queue/pure.rs index f15a1c8..8cf4912 100644 --- a/src/queue/pure.rs +++ b/src/queue/pure.rs @@ -1,77 +1,12 @@ -use pyo3::{ - exceptions::{PyIndexError, PyStopIteration, PyTypeError}, - prelude::*, - types::PyType, -}; - -use crate::backing::{ - containers::PyItem, - pure::{BinaryHeap, PureBacking}, -}; +use pyo3::prelude::*; #[pyclass] -pub struct PureQueue { - backing: Box>, -} +pub struct PureQueue {} -/// A "pure" priority queue just contains the priorities without any linked data, and does not support arbitrary deletions or priority updates #[pymethods] impl PureQueue { #[new] - #[pyo3(signature = (items=None))] - fn new(items: Option>) -> PyResult { - if let Some(py_object) = items { - Python::with_gil(|py| { - if let Ok(vec) = py_object.extract::>>(py) { - Ok(Self { - backing: Box::new(BinaryHeap::from_iter(vec.into_iter().map(PyItem::new))), - }) - } else { - Err(PyErr::new::("Items was not a sequence")) - } - }) - } else { - Ok(Self { - backing: Box::new(BinaryHeap::new()), - }) - } - } - - fn insert(mut self_: PyRefMut<'_, Self>, item: Py) { - self_.backing.add(PyItem::new(item)); - } - - // Below methods are identical to PairedQueue - // Normally I'd try to solve using a trait with default implementations but that doesn't seem to play nice with #[pymethods] - // (Although I shouldn't rule it out) - - /// Enables generic typing - #[classmethod] - fn __class_getitem__(cls_: Bound<'_, PyType>, _key: Py) -> Bound<'_, PyType> { - cls_ - } - - fn __len__(self_: PyRef<'_, Self>) -> usize { - self_.backing.len() - } - - fn __iter__(self_: PyRef<'_, Self>) -> PyRef<'_, Self> { - self_ - } - - fn __next__(mut self_: PyRefMut<'_, Self>) -> PyResult> { - if let Some(item) = self_.backing.pop() { - Ok(item.data()) - } else { - Err(PyErr::new::(())) - } - } - - fn pop(mut self_: PyRefMut<'_, Self>) -> PyResult> { - if let Some(item) = self_.backing.pop() { - Ok(item.data()) - } else { - Err(PyErr::new::(())) - } + fn new() -> Self { + Self {} } } diff --git a/tests/indexed_queue.py b/tests/indexed_queue.py index 37b7cc2..c829962 100644 --- a/tests/indexed_queue.py +++ b/tests/indexed_queue.py @@ -27,7 +27,7 @@ def test_empty_creation(): {"a": 0, "b": 1, "c": 2}, )) def test_creation(items: IndexedQueueInitializer): - queue = IndexedQueue(items) + queue = IndexedQueue() assert len(queue) == len(items) diff --git a/tests/pure_queue.py b/tests/pure_queue.py index 4e709db..559fe9f 100644 --- a/tests/pure_queue.py +++ b/tests/pure_queue.py @@ -17,7 +17,7 @@ def test_empty_creation(): @pytest.mark.parametrize("items", ([], [0, 1, 2], (0, 1, 2), (0.0, 1.0, 2.0), range(100))) def test_creation(items: PureQueueInitializer): - queue = PureQueue(items) + queue = PureQueue() assert len(queue) == len(items) @@ -76,4 +76,4 @@ def test_mixed_iteration(): results.append(number) if len(queue): queue.insert(queue.pop() * 2) - assert results == [2, 6, 8, 32] + assert results == [2, 6, 8, 16]