Compare commits
5 commits
b4ca806d76
...
3d91627253
Author | SHA1 | Date | |
---|---|---|---|
3d91627253 | |||
6878652583 | |||
6e91aef421 | |||
17b544f8bc | |||
ffe6a76241 |
14 changed files with 343 additions and 39 deletions
|
@ -25,6 +25,11 @@ class Queue[Data](ABC):
|
||||||
:return: The next item in the queue
|
:return: The next item in the queue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def pop(self) -> Data:
|
||||||
|
"""
|
||||||
|
:return: The next item in the queue
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class PureQueue[Data: Comparable](Queue[Data]):
|
class PureQueue[Data: Comparable](Queue[Data]):
|
||||||
"""
|
"""
|
||||||
|
@ -36,18 +41,13 @@ class PureQueue[Data: Comparable](Queue[Data]):
|
||||||
:param items: An optional list of priorities with which to initialize the queue
|
:param items: An optional list of priorities with which to initialize the queue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def pop(self) -> Data:
|
|
||||||
"""
|
|
||||||
:return: The next item in the queue
|
|
||||||
"""
|
|
||||||
|
|
||||||
def insert(self, item: Data) -> None:
|
def insert(self, item: Data) -> None:
|
||||||
"""
|
"""
|
||||||
:param item: Item to insert into the queue
|
:param item: Item to insert into the queue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class KeyedQueue[Data, Priority: Comparable](Queue[Data]):
|
class PairedQueue[Data, Priority: Comparable](Queue[Data]):
|
||||||
"""
|
"""
|
||||||
A min-queue that allows arbitrary data associated with some priority, allowing duplicates of both data and priority.
|
A min-queue that allows arbitrary data associated with some priority, allowing duplicates of both data and priority.
|
||||||
"""
|
"""
|
||||||
|
@ -87,3 +87,9 @@ class IndexedQueue[Data: Hashable, Priority: Comparable](Queue[Data]):
|
||||||
"""
|
"""
|
||||||
:param key: The item to delete from the queue
|
:param key: The item to delete from the queue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __contains__(self, key: Data) -> bool:
|
||||||
|
"""
|
||||||
|
:param key: The item to check the existence of
|
||||||
|
:return: Whether the item is in the queue
|
||||||
|
"""
|
||||||
|
|
12
src/backing/indexed/mod.rs
Normal file
12
src/backing/indexed/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/// Data structures for the "indexed" min-queues, supporting priority updates and arbitrary removals, but no duplicates
|
||||||
|
use super::{item::Item, pure::PureBacking};
|
||||||
|
|
||||||
|
/// A data structure usable for backing an "indexed" queue
|
||||||
|
pub trait IndexedBacking<D: Clone + Send + Sync, P: Ord + Clone + Send + Sync>:
|
||||||
|
PureBacking<Item<D, P>>
|
||||||
|
{
|
||||||
|
/// Update an item's priority
|
||||||
|
fn update(data: D, priority: P) -> Result<(), ()>;
|
||||||
|
/// Remove an item from the queue
|
||||||
|
fn remove(data: D) -> bool;
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
/// Data structures for the "keyed" min-queues, supporting priority updates and arbitrary removals, but no duplicates
|
|
||||||
use super::{item::Item, pure::PureBacking};
|
|
||||||
|
|
||||||
/// A data structure usable for backing a "keyed" queue
|
|
||||||
pub trait KeyedBacking<D: Clone + Send + Sync, P: Ord + Clone + Send + Sync>:
|
|
||||||
PureBacking<Item<D, P>>
|
|
||||||
{
|
|
||||||
/// Update an item's priority
|
|
||||||
fn update(data: D, priority: P) -> Result<(), ()>;
|
|
||||||
/// Remove an item from the queue
|
|
||||||
fn remove(data: D) -> bool;
|
|
||||||
}
|
|
|
@ -1,3 +1,3 @@
|
||||||
|
pub mod indexed;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
pub mod keyed;
|
|
||||||
pub mod pure;
|
pub mod pure;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/// Data structures for the "pure" min-queues, supporting duplicates but no arbitrary updates
|
/// Data structures for the "pure" min-queues, supporting duplicates but no arbitrary updates
|
||||||
pub mod binary_heap;
|
mod binary_heap;
|
||||||
|
pub use binary_heap::BinaryHeap;
|
||||||
|
|
||||||
/// A data structure usable for backing a "pure" queue
|
/// A data structure usable for backing a "pure" queue
|
||||||
pub trait PureBacking<T: Ord + Send + Sync>: Send + Sync {
|
pub trait PureBacking<T: Ord + Send + Sync>: Send + Sync {
|
||||||
|
|
|
@ -2,11 +2,13 @@ pub mod backing;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use queue::pure::PureQueue;
|
use queue::{IndexedQueue, PairedQueue, PureQueue};
|
||||||
|
|
||||||
/// Bindings for the Rust queue implementations
|
/// Bindings for the Rust queue implementations
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
fn pyority_queue(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
fn pyority_queue(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
m.add_class::<PureQueue>()?;
|
m.add_class::<PureQueue>()?;
|
||||||
|
m.add_class::<PairedQueue>()?;
|
||||||
|
m.add_class::<IndexedQueue>()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
12
src/queue/indexed.rs
Normal file
12
src/queue/indexed.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
pub struct IndexedQueue {}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl IndexedQueue {
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,7 @@
|
||||||
pub mod pure;
|
mod indexed;
|
||||||
|
mod paired;
|
||||||
|
mod pure;
|
||||||
|
|
||||||
|
pub use indexed::IndexedQueue;
|
||||||
|
pub use paired::PairedQueue;
|
||||||
|
pub use pure::PureQueue;
|
||||||
|
|
25
src/queue/paired.rs
Normal file
25
src/queue/paired.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// A "pure" priority queue that supports duplicates, but not arbitrary deletions or weight updates
|
||||||
|
use crate::backing::{
|
||||||
|
item::Item,
|
||||||
|
pure::{BinaryHeap, PureBacking},
|
||||||
|
};
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
pub struct PairedQueue {
|
||||||
|
backing: Box<dyn PureBacking<Item<Py<PyAny>, f64>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PairedQueue {
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
backing: Box::new(BinaryHeap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __len__(self_: PyRef<'_, Self>) -> usize {
|
||||||
|
self_.backing.len()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,12 @@
|
||||||
// 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::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
pub struct PureQueue {
|
pub struct PureQueue {}
|
||||||
backing: Box<dyn PureBacking<Item<Py<PyAny>, f64>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PureQueue {
|
impl PureQueue {
|
||||||
#[new]
|
#[new]
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {}
|
||||||
backing: Box::new(BinaryHeap::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn __len__(self_: PyRef<'_, Self>) -> usize {
|
|
||||||
self_.backing.len()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
132
tests/indexed_queue.py
Normal file
132
tests/indexed_queue.py
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from pyority_queue import IndexedQueue
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pyority_queue import Comparable
|
||||||
|
|
||||||
|
|
||||||
|
type IndexedQueueInitializer = dict[Any, Comparable] | list[tuple[Any, Comparable]] | tuple[tuple[Any, Comparable], ...]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_creation():
|
||||||
|
queue = IndexedQueue()
|
||||||
|
assert len(queue) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("items", (
|
||||||
|
[],
|
||||||
|
[("a", 0), ("b", 1), ("c", 2)],
|
||||||
|
((0, 0), (1, 1), (2, 2)),
|
||||||
|
((0.0, 0.0), (1.0, 1.0), (2.0, 2.0)),
|
||||||
|
((lambda: None, 0)),
|
||||||
|
((Exception(), 0.0)),
|
||||||
|
(([], -1)),
|
||||||
|
{},
|
||||||
|
{"a": 0, "b": 1, "c": 2},
|
||||||
|
))
|
||||||
|
def test_creation(items: IndexedQueueInitializer):
|
||||||
|
queue = IndexedQueue()
|
||||||
|
assert len(queue) == len(items)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("items", (
|
||||||
|
[],
|
||||||
|
((0, 0),),
|
||||||
|
((-1, -1), (3, 3)),
|
||||||
|
))
|
||||||
|
def test_iteration(items: IndexedQueueInitializer):
|
||||||
|
queue = IndexedQueue(items)
|
||||||
|
assert len(list(queue)) == len(items)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("items", (
|
||||||
|
[],
|
||||||
|
(("a", -3), ("b", 5)),
|
||||||
|
(("c", 3.0), ("b", 2.0), ("a", 1.0)),
|
||||||
|
(("c", 3), ("f", 6), ("h", 8), ("e", 5), ("g", 7), ("d", 4), ("b", 2), ("a", 0)),
|
||||||
|
))
|
||||||
|
def test_sorting(items: IndexedQueueInitializer):
|
||||||
|
queue = IndexedQueue(items)
|
||||||
|
in_order = list(queue)
|
||||||
|
assert in_order == sorted(in_order)
|
||||||
|
|
||||||
|
|
||||||
|
def test_insertion():
|
||||||
|
queue = IndexedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
queue["d"] = 4
|
||||||
|
queue["e"] = 5
|
||||||
|
assert list(queue) == ["a", "b", "c", "d", "e"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_removal():
|
||||||
|
queue = IndexedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
assert queue.pop() == "a"
|
||||||
|
assert queue.pop() == "b"
|
||||||
|
assert queue.pop() == "c"
|
||||||
|
assert len(queue) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_removal():
|
||||||
|
queue = IndexedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
assert queue.pop() == "a"
|
||||||
|
assert list(queue) == ["b", "c"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_removal():
|
||||||
|
queue = IndexedQueue()
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
queue.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicates():
|
||||||
|
queue = IndexedQueue[str, int]((("a", 0), ("a", 0), ("a", 2)))
|
||||||
|
queue["b"] = 1
|
||||||
|
queue["b"] = 3
|
||||||
|
assert list(queue) == ["a", "b"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_deletion():
|
||||||
|
queue = IndexedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
del queue["a"]
|
||||||
|
del queue["b"]
|
||||||
|
assert list(queue) == ["c"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_deletion():
|
||||||
|
queue = IndexedQueue()
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
del queue["a"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_in():
|
||||||
|
queue = IndexedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
assert "a" in queue
|
||||||
|
assert "b" in queue
|
||||||
|
assert "c" in queue
|
||||||
|
assert "d" not in queue
|
||||||
|
del queue["b"]
|
||||||
|
assert "b" not in queue
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_iteration():
|
||||||
|
queue = IndexedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
results = []
|
||||||
|
for char in queue:
|
||||||
|
results.append(char)
|
||||||
|
if len(queue):
|
||||||
|
queue[queue.pop()] = 10
|
||||||
|
assert results == ["a", "c", "b"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_iteration_deletion():
|
||||||
|
queue = IndexedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
results = []
|
||||||
|
for char in queue:
|
||||||
|
results.append(char)
|
||||||
|
if "b" in queue:
|
||||||
|
del queue["b"]
|
||||||
|
assert results == ["a", "c"]
|
||||||
|
|
||||||
|
|
97
tests/paired_queue.py
Normal file
97
tests/paired_queue.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from pyority_queue import PairedQueue
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pyority_queue import Comparable
|
||||||
|
|
||||||
|
|
||||||
|
type PairedQueueInitializer = dict[Any, Comparable] | list[tuple[Any, Comparable]] | tuple[tuple[Any, Comparable], ...]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_creation():
|
||||||
|
queue = PairedQueue()
|
||||||
|
assert len(queue) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("items", (
|
||||||
|
[],
|
||||||
|
[("a", 0), ("b", 1), ("c", 2)],
|
||||||
|
((0, 0), (1, 1), (2, 2)),
|
||||||
|
((0.0, 0.0), (1.0, 1.0), (2.0, 2.0)),
|
||||||
|
((lambda: None, 0)),
|
||||||
|
((Exception(), 0.0)),
|
||||||
|
(([], -1)),
|
||||||
|
{},
|
||||||
|
{"a": 0, "b": 1, "c": 2},
|
||||||
|
))
|
||||||
|
def test_creation(items: PairedQueueInitializer):
|
||||||
|
queue = PairedQueue()
|
||||||
|
assert len(queue) == len(items)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("items", (
|
||||||
|
[],
|
||||||
|
((0, 0),),
|
||||||
|
((-1, -1), (3, 3)),
|
||||||
|
))
|
||||||
|
def test_iteration(items: PairedQueueInitializer):
|
||||||
|
queue = PairedQueue(items)
|
||||||
|
assert len(list(queue)) == len(items)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("items", (
|
||||||
|
[],
|
||||||
|
(("a", -3), ("b", 5)),
|
||||||
|
(("c", 3.0), ("b", 2.0), ("a", 1.0)),
|
||||||
|
(("c", 3), ("f", 6), ("h", 8), ("e", 5), ("g", 7), ("d", 4), ("b", 2), ("a", 0)),
|
||||||
|
))
|
||||||
|
def test_sorting(items: PairedQueueInitializer):
|
||||||
|
queue = PairedQueue(items)
|
||||||
|
in_order = list(queue)
|
||||||
|
assert in_order == sorted(in_order)
|
||||||
|
|
||||||
|
|
||||||
|
def test_insertion():
|
||||||
|
queue = PairedQueue({"a": 1, "b": 2, "c": 3})
|
||||||
|
queue["d"] = 4
|
||||||
|
queue["e"] = 5
|
||||||
|
assert list(queue) == ["a", "b", "c", "d", "e"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_removal():
|
||||||
|
queue = PairedQueue[str, int]({"a": 1, "b": 2, "c": 3})
|
||||||
|
assert queue.pop() == "a"
|
||||||
|
assert queue.pop() == "b"
|
||||||
|
assert queue.pop() == "c"
|
||||||
|
assert len(queue) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_removal():
|
||||||
|
queue = PairedQueue[str, int]({"a": 1, "b": 2, "c": 3})
|
||||||
|
assert queue.pop() == "a"
|
||||||
|
assert list(queue) == ["b", "c"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_removal():
|
||||||
|
queue = PairedQueue()
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
queue.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicates():
|
||||||
|
queue = PairedQueue[str, int]((("a", 0), ("a", 0), ("a", 2)))
|
||||||
|
queue["b"] = 1
|
||||||
|
queue["b"] = 3
|
||||||
|
assert list(queue) == ["a", "a", "b", "a", "b"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_iteration():
|
||||||
|
queue = PairedQueue[str, int]({"a": 1, "b": 2, "c": 3})
|
||||||
|
results = []
|
||||||
|
for char in queue:
|
||||||
|
results.append(char)
|
||||||
|
if len(queue):
|
||||||
|
queue[queue.pop()] = 10
|
||||||
|
assert results == ["a", "c", "b"]
|
|
@ -1,6 +1,6 @@
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pyority_queue::backing::pure::{binary_heap::BinaryHeap, PureBacking};
|
use pyority_queue::backing::pure::{BinaryHeap, PureBacking};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pure_binary_heap_manual_creation() {
|
fn test_pure_binary_heap_manual_creation() {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pyority_queue import PureQueue
|
from pyority_queue import PureQueue
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pyority_queue import Comparable
|
||||||
|
|
||||||
|
|
||||||
type PureQueueInitializer = list[int | float] | tuple[int | float, ...]
|
type PureQueueInitializer = list[Comparable] | tuple[Comparable, ...]
|
||||||
|
|
||||||
|
|
||||||
def test_empty_creation():
|
def test_empty_creation():
|
||||||
|
@ -16,6 +20,7 @@ def test_creation(items: PureQueueInitializer):
|
||||||
queue = PureQueue()
|
queue = PureQueue()
|
||||||
assert len(queue) == len(items)
|
assert len(queue) == len(items)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("items", ([], (0,), (-1, 3), range(100)))
|
@pytest.mark.parametrize("items", ([], (0,), (-1, 3), range(100)))
|
||||||
def test_iteration(items: PureQueueInitializer):
|
def test_iteration(items: PureQueueInitializer):
|
||||||
queue = PureQueue(items)
|
queue = PureQueue(items)
|
||||||
|
@ -36,8 +41,39 @@ def test_insertion():
|
||||||
assert list(queue) == [0, 2, 3, 4, 6, 7, 8]
|
assert list(queue) == [0, 2, 3, 4, 6, 7, 8]
|
||||||
|
|
||||||
|
|
||||||
|
def test_removal():
|
||||||
|
queue = PureQueue[int]((4, 2, 8, 6))
|
||||||
|
assert queue.pop() == 2
|
||||||
|
assert queue.pop() == 4
|
||||||
|
assert queue.pop() == 6
|
||||||
|
assert queue.pop() == 8
|
||||||
|
assert len(queue) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_removal():
|
||||||
|
queue = PureQueue[int]((4, 2, 8, 6))
|
||||||
|
assert queue.pop() == 2
|
||||||
|
assert list(queue) == [4, 6, 8]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_removal():
|
||||||
|
queue = PureQueue()
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
queue.pop()
|
||||||
|
|
||||||
|
|
||||||
def test_duplicates():
|
def test_duplicates():
|
||||||
queue = PureQueue[int]((0, 0, 0, 5, 5))
|
queue = PureQueue[int]((0, 0, 0, 5, 5))
|
||||||
queue.insert(3)
|
queue.insert(3)
|
||||||
queue.insert(3)
|
queue.insert(3)
|
||||||
assert list(queue) == [0, 0, 0, 3, 3, 5, 5]
|
assert list(queue) == [0, 0, 0, 3, 3, 5, 5]
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_iteration():
|
||||||
|
queue = PureQueue[int]((4, 2, 8, 6))
|
||||||
|
results = []
|
||||||
|
for number in queue:
|
||||||
|
results.append(number)
|
||||||
|
if len(queue):
|
||||||
|
queue.insert(queue.pop() * 2)
|
||||||
|
assert results == [2, 6, 8, 16]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue