Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Declare minimum Rust version #64

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ keywords = ["priority", "queue", "heap"]
categories = ["data-structures", "algorithms"]
license = "LGPL-3.0-or-later OR MPL-2.0"
edition = "2024"
rust-version = "1.85"

[build-dependencies]
autocfg = "1"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
[![crate](https://img.shields.io/crates/v/priority-queue.svg)](https://crates.io/crates/priority-queue)
[![Build](https://github.com/garro95/priority-queue/actions/workflows/build.yml/badge.svg)](https://github.com/garro95/priority-queue/actions/workflows/build.yml)
[![Test](https://github.com/garro95/priority-queue/actions/workflows/test.yml/badge.svg)](https://github.com/garro95/priority-queue/actions/workflows/test.yml)
![MSRV](https://img.shields.io/crates/msrv/priority-queue)

This crate implements a Priority Queue with a function to change the priority of an object.
Priority and items are stored in an `IndexMap` and the queue is implemented as a Heap of indexes.
83 changes: 83 additions & 0 deletions src/double_priority_queue/iterators.rs
Original file line number Diff line number Diff line change
@@ -40,6 +40,89 @@ use std::iter::*;

use crate::DoublePriorityQueue;

use super::Index;

/// An `Iterator` in arbitrary order which uses a `predicate` to determine if
/// an element should be removed from the `DoublePriorityQueue`.
///
/// It can be obtained calling the [`extract_if`](DoublePriorityQueue::extract_if) method.
///
/// The `predicate` has mutable access to the `(item, priority)` pairs.
///
/// It can update the priorities of the elements in the queue and the items
/// in a way that does not change the result of any of `hash` or `eq`.
///
/// When the iterator goes out of scope, the heap is rebuilt to restore the
/// structural properties.
#[cfg(feature = "std")]
pub struct ExtractIf<'a, I: 'a, P: 'a, F, H: 'a = RandomState>
where
P: Ord,
{
pq: &'a mut DoublePriorityQueue<I, P, H>,
predicate: F,
idx: Index,
}

#[cfg(not(feature = "std"))]
pub struct ExtractIf<'a, I: 'a, P: 'a, F, H: 'a>
where
P: Ord,
{
pq: &'a mut DoublePriorityQueue<I, P, H>,
predicate: F,
idx: Index,
}

impl<'a, I: 'a, P: 'a, F, H: 'a> ExtractIf<'a, I, P, F, H>
where
P: Ord,
{
pub(crate) fn new(pq: &'a mut DoublePriorityQueue<I, P, H>, predicate: F) -> Self {
ExtractIf {
pq,
predicate,
idx: Index(0),
}
}
}

impl<'a, I: 'a, P: 'a, F, H: 'a> Iterator for ExtractIf<'a, I, P, F, H>
where
P: Ord,
F: FnMut(&mut I, &mut P) -> bool,
H: BuildHasher,
{
type Item = (I, P);
fn next(&mut self) -> Option<Self::Item> {
use indexmap::map::MutableKeys;

loop {
let r: Option<bool> = self
.pq
.store
.map
.get_index_mut2(self.idx.0)
.map(|(i, p)| (self.predicate)(i, p));

match r {
Some(true) => return self.pq.store.swap_remove_index(self.idx),
Some(false) => self.idx.0 += 1,
None => return None,
}
}
}
}

impl<'a, I: 'a, P: 'a, F, H: 'a> Drop for ExtractIf<'a, I, P, F, H>
where
P: Ord,
{
fn drop(&mut self) {
self.pq.heap_build();
}
}

/// A mutable iterator over the couples `(item, priority)` of the `DoublePriorityQueue`
/// in arbitrary order.
///
23 changes: 23 additions & 0 deletions src/double_priority_queue/mod.rs
Original file line number Diff line number Diff line change
@@ -454,6 +454,29 @@ where
self.heap_build();
}

/// Returns an `Iterator` removing from the queue the `(item, priority)`
/// pairs for which the `predicate` returns `true`, in arbitraty order.
///
/// The `predicate` receives mutable references to both the item and
/// the priority.
///
/// It's a logical error to change the item in a way
/// that changes the result of `Hash` or `Eq`.
///
/// The `predicate` can change the priority. If it returns `true`, the
/// extracted pair will have the updated priority, otherwise, the
/// heap structural property will be restored once the iterator is `Drop`ped.
///
/// # Example
/// ```
/// ```
pub fn extract_if<F>(&mut self, predicate: F) -> ExtractIf<I, P, F, H>
where
F: FnMut(&mut I, &mut P) -> bool,
{
ExtractIf::new(self, predicate)
}

/// Removes the item with the lowest priority from
/// the priority queue if the predicate returns `true`.
///
83 changes: 83 additions & 0 deletions src/priority_queue/iterators.rs
Original file line number Diff line number Diff line change
@@ -40,6 +40,89 @@ use std::iter::*;

use crate::PriorityQueue;

use super::Index;

/// An `Iterator` in arbitrary order which uses a `predicate` to determine if
/// an element should be removed from the `PriorityQueue`.
///
/// It can be obtained calling the [`extract_if`](PriorityQueue::extract_if) method.
///
/// The `predicate` has mutable access to the `(item, priority)` pairs.
///
/// It can update the priorities of the elements in the queue and the items
/// in a way that does not change the result of any of `hash` or `eq`.
///
/// When the iterator goes out of scope, the heap is rebuilt to restore the
/// structural properties.
#[cfg(feature = "std")]
pub struct ExtractIf<'a, I: 'a, P: 'a, F, H: 'a = RandomState>
where
P: Ord,
{
pq: &'a mut PriorityQueue<I, P, H>,
predicate: F,
idx: Index,
}

#[cfg(not(feature = "std"))]
pub struct ExtractIf<'a, I: 'a, P: 'a, F, H: 'a>
where
P: Ord,
{
pq: &'a mut PriorityQueue<I, P, H>,
predicate: F,
idx: Index,
}

impl<'a, I: 'a, P: 'a, F, H: 'a> ExtractIf<'a, I, P, F, H>
where
P: Ord,
{
pub(crate) fn new(pq: &'a mut PriorityQueue<I, P, H>, predicate: F) -> Self {
ExtractIf {
pq,
predicate,
idx: Index(0),
}
}
}

impl<'a, I: 'a, P: 'a, F, H: 'a> Iterator for ExtractIf<'a, I, P, F, H>
where
P: Ord,
F: FnMut(&mut I, &mut P) -> bool,
H: BuildHasher,
{
type Item = (I, P);
fn next(&mut self) -> Option<Self::Item> {
use indexmap::map::MutableKeys;

loop {
let r: Option<bool> = self
.pq
.store
.map
.get_index_mut2(self.idx.0)
.map(|(i, p)| (self.predicate)(i, p));

match r {
Some(true) => return self.pq.store.swap_remove_index(self.idx),
Some(false) => self.idx.0 += 1,
None => return None,
}
}
}
}

impl<'a, I: 'a, P: 'a, F, H: 'a> Drop for ExtractIf<'a, I, P, F, H>
where
P: Ord,
{
fn drop(&mut self) {
self.pq.heap_build();
}
}

/// A mutable iterator over the couples `(item, priority)` of the `PriorityQueue`
/// in arbitrary order.
///
19 changes: 19 additions & 0 deletions src/priority_queue/mod.rs
Original file line number Diff line number Diff line change
@@ -364,6 +364,25 @@ where
self.heap_build();
}

/// Returns an `Iterator` removing from the queue the `(item, priority)`
/// pairs for which the `predicate` returns `true`, in arbitraty order.
///
/// The `predicate` receives mutable references to both the item and
/// the priority.
///
/// It's a logical error to change the item in a way
/// that changes the result of `Hash` or `Eq`.
///
/// The `predicate` can change the priority. If it returns `true`, the
/// extracted pair will have the updated priority, otherwise, the
/// heap structural property will be restored once the iterator is `Drop`ped.
pub fn extract_if<F>(&mut self, predicate: F) -> ExtractIf<I, P, F, H>
where
F: FnMut(&mut I, &mut P) -> bool,
{
ExtractIf::new(self, predicate)
}

/// Removes the item with the greatest priority from
/// the priority queue if the `predicate` returns `true`.
///
35 changes: 35 additions & 0 deletions src/store.rs
Original file line number Diff line number Diff line change
@@ -267,6 +267,41 @@ impl<I, P, H> Store<I, P, H> {
self.heap.swap(a.0, b.0);
}

/// Remove and return the element at index `idx`
/// and swap it with the last element keeping a consistent
/// state.
///
/// Computes in **O(1)** time (average)
pub fn swap_remove_index(&mut self, idx: Index) -> Option<(I, P)> {
// swap_remove the position from the qp
let position = self.qp.swap_remove(idx.0);
self.size -= 1;

if idx.0 < self.size {
// SAFETY: head validity checked on the previous line.
// All positions point to valid heap items because we already
// updated the qp.
unsafe {
*self.heap.get_unchecked_mut(self.qp.get_unchecked(idx.0).0) = idx;
}
}
self.heap.swap_remove(position.0);
// Fix indexes and swap remove the old heap head from the qp
if position.0 < self.size {
// SAFETY: position validity checked on the previous line.
// Indexes still point to valid qp items because we didn't
// remove anything from qp yet
unsafe {
*self
.qp
.get_unchecked_mut(self.heap.get_unchecked(position.0).0) = position;
}
}

// swap remove from the map and return to the client
self.map.swap_remove_index(idx.0)
}

/// Remove and return the element in position `position`
/// and swap it with the last element keeping a consistent
/// state.
73 changes: 68 additions & 5 deletions tests/double_priority_queue.rs
Original file line number Diff line number Diff line change
@@ -786,12 +786,11 @@ mod doublepq_tests {
assert_eq!(pq.pop_max(), Some(("b", 20)));

/*
As expected, this does not compile
// As expected, this does not compile
let iter_mut = pq.iter_mut();
iter_mut.for_each(|(_, p)| {*p += 2});

assert_eq!(pq.pop_max(), Some(("f", 9)));
*/
assert_eq!(pq.pop_max(), Some(("a", 21)));
iter_mut.for_each(|(_, p)| {*p += 2}); */
}

#[test]
@@ -819,7 +818,6 @@ mod doublepq_tests {
pq.push(Animal::new("bird".to_string(), true, false), 7);
pq.push(Animal::new("fish".to_string(), false, true), 4);
pq.push(Animal::new("cow".to_string(), false, false), 3);

pq.retain(|i, _| i.can_swim);

assert_eq!(
@@ -873,6 +871,71 @@ mod doublepq_tests {
);
}

#[test]
fn extract_if() {
#[derive(Hash, PartialEq, Eq, Debug)]
struct Animal {
name: String,
can_fly: bool,
can_swim: bool,
}

impl Animal {
pub fn new(name: String, can_fly: bool, can_swim: bool) -> Self {
Animal {
name,
can_fly,
can_swim,
}
}
}

let mut pq = DoublePriorityQueue::new();
pq.push(Animal::new("dog".to_string(), false, true), 1);
pq.push(Animal::new("cat".to_string(), false, false), 2);
pq.push(Animal::new("bird".to_string(), true, false), 7);
pq.push(Animal::new("fish".to_string(), false, true), 4);
pq.push(Animal::new("cow".to_string(), false, false), 3);

let swimming_animals: Vec<(Animal, i32)> = pq
.extract_if(|i, p| {
if i.can_fly {
*p -= 18;
return false;
}

i.can_swim
})
.collect();

assert_eq!(
swimming_animals,
[
(Animal::new("dog".to_string(), false, true), 1),
(Animal::new("fish".to_string(), false, true), 4)
]
);
assert_eq!(
pq.pop_max(),
Some((Animal::new("cow".to_string(), false, false), 3))
);
assert_eq!(
pq.pop_max(),
Some((Animal::new("cat".to_string(), false, false), 2))
);
assert_eq!(
pq.pop_max(),
Some((Animal::new("bird".to_string(), true, false), -11))
);

/*
// As expected, this does not compile
let extract_if = pq.extract_if(|i, p| { i.can_fly });

assert_eq!(pq.pop_max(), None);
extract_if.for_each(|(_, p)| println!("{:?}", p)); */
}

#[test]
fn into_sorted_iter() {
let mut pq = DoublePriorityQueue::new();
Loading