From ff18f71150698ba8ea823b8cbad6cabb02eb5477 Mon Sep 17 00:00:00 2001 From: orxfun Date: Fri, 28 Aug 2026 14:10:19 +0200 Subject: [PATCH 1/3] feat(heap): add linear-time bulk construction --- src/dary/daryheap.rs | 15 ++++++++++- src/dary/daryheap_index.rs | 18 ++++++++++--- src/dary/daryheap_map.rs | 33 +++++++++++++++++++----- src/dary/heap.rs | 29 ++++++++++++++++++++- src/impl_queues/priority_queue.rs | 16 +++++++++--- src/impl_queues/std_binary_heap.rs | 14 ++++++++-- tests/daryheap.rs | 18 +++++++++++++ tests/daryheap_of_indices.rs | 19 ++++++++++++++ tests/daryheap_with_map.rs | 18 +++++++++++++ tests/priority_queue_deckey_tests/mod.rs | 2 +- 10 files changed, 164 insertions(+), 18 deletions(-) diff --git a/src/dary/daryheap.rs b/src/dary/daryheap.rs index 4b6afed..d48e0d5 100644 --- a/src/dary/daryheap.rs +++ b/src/dary/daryheap.rs @@ -1,5 +1,5 @@ use super::heap::Heap; -use crate::{positions::none::HeapPositionsNone, PriorityQueue}; +use crate::{PriorityQueue, positions::none::HeapPositionsNone}; /// Type alias for `DaryHeap`; see [`DaryHeap`] for details. pub type BinaryHeap = DaryHeap; @@ -75,6 +75,19 @@ where } } } + +impl FromIterator<(N, K)> for DaryHeap +where + N: Clone, + K: PartialOrd + Clone, +{ + fn from_iter>(iter: I) -> Self { + Self { + heap: Heap::from_iter(iter, HeapPositionsNone), + } + } +} + impl DaryHeap where N: Clone, diff --git a/src/dary/daryheap_index.rs b/src/dary/daryheap_index.rs index 8831a91..9ea62b6 100644 --- a/src/dary/daryheap_index.rs +++ b/src/dary/daryheap_index.rs @@ -1,8 +1,6 @@ use super::heap::Heap; -use crate::{ - positions::has_index::HeapPositionsHasIndex, HasIndex, PriorityQueue, PriorityQueueDecKey, - ResUpdateKey, -}; +use crate::positions::has_index::HeapPositionsHasIndex; +use crate::{HasIndex, PriorityQueue, PriorityQueueDecKey, ResUpdateKey}; /// Type alias for `DaryHeapOfIndices`; see [`DaryHeapOfIndices`] for details. pub type BinaryHeapOfIndices = DaryHeapOfIndices; @@ -147,6 +145,18 @@ where N: HasIndex, K: PartialOrd + Clone, { + /// Creates a d-ary heap from an iterator in linear time. + /// + /// The `index_bound` is the exclusive upper bound of node indices that may enter the heap. + pub fn from_iter_with_index_bound(index_bound: usize, iter: I) -> Self + where + I: IntoIterator, + { + Self { + heap: Heap::from_iter(iter, HeapPositionsHasIndex::with_index_bound(index_bound)), + } + } + /// As explained in [`DaryHeapOfIndices`], /// this heap is useful when the nodes come from a closed set with a known size. /// Therefore, the heap has a strict exclusive upper bound on the index of a node which can enter the heap, diff --git a/src/dary/daryheap_map.rs b/src/dary/daryheap_map.rs index 9363a81..c6a0308 100644 --- a/src/dary/daryheap_map.rs +++ b/src/dary/daryheap_map.rs @@ -1,8 +1,6 @@ use super::heap::Heap; -use crate::{ - positions::map::{HeapPositionsMap, Index}, - PriorityQueue, PriorityQueueDecKey, ResUpdateKey, -}; +use crate::positions::map::{HeapPositionsMap, Index}; +use crate::{PriorityQueue, PriorityQueueDecKey, ResUpdateKey}; /// Type alias for `DaryHeapWithMap`; see [`DaryHeapWithMap`] for details. pub type BinaryHeapWithMap = DaryHeapWithMap; @@ -139,6 +137,19 @@ where } } } + +impl FromIterator<(N, K)> for DaryHeapWithMap +where + N: Index, + K: PartialOrd + Clone, +{ + fn from_iter>(iter: I) -> Self { + Self { + heap: Heap::from_iter(iter, HeapPositionsMap::default()), + } + } +} + impl DaryHeapWithMap where N: Index, @@ -207,8 +218,18 @@ where N: Index, K: PartialOrd + Clone, { - type NodeKey<'a> = &'a (N, K) where Self: 'a, N: 'a, K: 'a; - type Iter<'a> = core::slice::Iter<'a, (N, K)> where Self: 'a, N: 'a, K: 'a; + type NodeKey<'a> + = &'a (N, K) + where + Self: 'a, + N: 'a, + K: 'a; + type Iter<'a> + = core::slice::Iter<'a, (N, K)> + where + Self: 'a, + N: 'a, + K: 'a; #[inline(always)] fn len(&self) -> usize { diff --git a/src/dary/heap.rs b/src/dary/heap.rs index ff44828..988d709 100644 --- a/src/dary/heap.rs +++ b/src/dary/heap.rs @@ -1,7 +1,7 @@ use super::daryheap_const_helpers::{left_child_of, offset, parent_of}; use crate::{ - positions::heap_positions::{HeapPositions, HeapPositionsDecKey}, PriorityQueue, PriorityQueueDecKey, ResUpdateKey, + positions::heap_positions::{HeapPositions, HeapPositionsDecKey}, }; use alloc::vec::Vec; @@ -30,6 +30,33 @@ where Self { tree, positions } } + pub fn from_iter(iter: I, mut positions: P) -> Self + where + I: IntoIterator, + { + let mut tree: Vec<_> = iter.into_iter().collect(); + + if let Some((node, key)) = tree.first().cloned() { + let heap_offset = offset::(); + let mut padded_tree = Vec::with_capacity(tree.len() + heap_offset); + padded_tree.extend((0..heap_offset).map(|_| (node.clone(), key.clone()))); + padded_tree.append(&mut tree); + tree = padded_tree; + + for position in heap_offset..tree.len() { + positions.insert(&tree[position].0, position); + } + + let mut heap = Self { tree, positions }; + for position in (heap_offset..heap.tree.len()).rev() { + heap.heapify_down(position); + } + heap + } else { + Self { tree, positions } + } + } + fn insert_offset(&mut self, node: &N, key: &K) { self.tree .extend((0..offset::()).map(|_| (node.clone(), key.clone()))); diff --git a/src/impl_queues/priority_queue.rs b/src/impl_queues/priority_queue.rs index 05019fb..909f6ee 100644 --- a/src/impl_queues/priority_queue.rs +++ b/src/impl_queues/priority_queue.rs @@ -1,5 +1,5 @@ use crate::{ - priority_queue::PriorityQueue, PriorityQueueDecKey, ResTryDecreaseKeyOrPush, ResUpdateKey, + PriorityQueueDecKey, ResTryDecreaseKeyOrPush, ResUpdateKey, priority_queue::PriorityQueue, }; use std::hash::Hash; @@ -8,8 +8,18 @@ where K: PartialOrd + Ord, N: Ord + Hash, { - type NodeKey<'a> = (&'a N, &'a K) where Self: 'a, N: 'a, K: 'a; - type Iter<'a> = priority_queue::core_iterators::Iter<'a, N, K> where Self: 'a, N: 'a, K: 'a; + type NodeKey<'a> + = (&'a N, &'a K) + where + Self: 'a, + N: 'a, + K: 'a; + type Iter<'a> + = priority_queue::core_iterators::Iter<'a, N, K> + where + Self: 'a, + N: 'a, + K: 'a; fn len(&self) -> usize { priority_queue::PriorityQueue::len(self) diff --git a/src/impl_queues/std_binary_heap.rs b/src/impl_queues/std_binary_heap.rs index e2c5800..895d3cd 100644 --- a/src/impl_queues/std_binary_heap.rs +++ b/src/impl_queues/std_binary_heap.rs @@ -5,8 +5,18 @@ where K: PartialOrd + Ord, N: Ord, { - type NodeKey<'a> = &'a (N, K) where Self: 'a, N: 'a, K: 'a; - type Iter<'a> = alloc::collections::binary_heap::Iter<'a, (N, K)> where Self: 'a, N: 'a, K: 'a; + type NodeKey<'a> + = &'a (N, K) + where + Self: 'a, + N: 'a, + K: 'a; + type Iter<'a> + = alloc::collections::binary_heap::Iter<'a, (N, K)> + where + Self: 'a, + N: 'a, + K: 'a; #[inline(always)] fn len(&self) -> usize { diff --git a/tests/daryheap.rs b/tests/daryheap.rs index f29104b..55018a5 100644 --- a/tests/daryheap.rs +++ b/tests/daryheap.rs @@ -31,3 +31,21 @@ fn test_dary_for() { test_push_then_pop(new_heap()); test_push_then_pop_randomized(new_heap()); } + +#[test] +fn from_iter_heapifies() { + use orx_priority_queue::PriorityQueue; + + let mut heap = [(0, 42), (1, 7), (2, 24), (3, 3), (4, 99), (5, 11)] + .into_iter() + .collect::>(); + + assert_eq!(heap.len(), 6); + assert_eq!(heap.pop(), Some((3, 3))); + assert_eq!(heap.pop(), Some((1, 7))); + assert_eq!(heap.pop(), Some((5, 11))); + assert_eq!(heap.pop(), Some((2, 24))); + assert_eq!(heap.pop(), Some((0, 42))); + assert_eq!(heap.pop(), Some((4, 99))); + assert_eq!(heap.pop(), None); +} diff --git a/tests/daryheap_of_indices.rs b/tests/daryheap_of_indices.rs index b0b9159..c4c210f 100644 --- a/tests/daryheap_of_indices.rs +++ b/tests/daryheap_of_indices.rs @@ -50,3 +50,22 @@ fn test_dary_for() { .iter() .for_each(|change_key_method| test_change_key_or_push(new_heap(), *change_key_method)); } + +#[test] +fn from_iter_with_index_bound_heapifies_and_tracks_positions() { + use orx_priority_queue::{DaryHeapOfIndices, PriorityQueue, PriorityQueueDecKey}; + + let mut heap = DaryHeapOfIndices::::from_iter_with_index_bound( + 4, + [(0, 42), (1, 7), (2, 24), (3, 3)], + ); + + assert_eq!(heap.key_of(&0), Some(42)); + assert_eq!(heap.key_of(&1), Some(7)); + assert_eq!(heap.key_of(&2), Some(24)); + assert_eq!(heap.key_of(&3), Some(3)); + assert_eq!(heap.pop(), Some((3, 3))); + assert_eq!(heap.pop(), Some((1, 7))); + assert_eq!(heap.pop(), Some((2, 24))); + assert_eq!(heap.pop(), Some((0, 42))); +} diff --git a/tests/daryheap_with_map.rs b/tests/daryheap_with_map.rs index 46a2a52..d08a6dd 100644 --- a/tests/daryheap_with_map.rs +++ b/tests/daryheap_with_map.rs @@ -50,3 +50,21 @@ fn test_dary_for() { .iter() .for_each(|change_key_method| test_change_key_or_push(new_heap(), *change_key_method)); } + +#[test] +fn from_iter_heapifies_and_tracks_positions() { + use orx_priority_queue::{PriorityQueue, PriorityQueueDecKey, QuaternaryHeapWithMap}; + + let mut heap = [(0, 42), (1, 7), (2, 24), (3, 3)] + .into_iter() + .collect::>(); + + assert_eq!(heap.key_of(&0), Some(42)); + assert_eq!(heap.key_of(&1), Some(7)); + assert_eq!(heap.key_of(&2), Some(24)); + assert_eq!(heap.key_of(&3), Some(3)); + assert_eq!(heap.pop(), Some((3, 3))); + assert_eq!(heap.pop(), Some((1, 7))); + assert_eq!(heap.pop(), Some((2, 24))); + assert_eq!(heap.pop(), Some((0, 42))); +} diff --git a/tests/priority_queue_deckey_tests/mod.rs b/tests/priority_queue_deckey_tests/mod.rs index 6799089..96910ff 100644 --- a/tests/priority_queue_deckey_tests/mod.rs +++ b/tests/priority_queue_deckey_tests/mod.rs @@ -5,7 +5,7 @@ mod key_of; mod mixed; mod remove; -pub use change_key::{test_change_key, ChangeKeyMethod}; +pub use change_key::{ChangeKeyMethod, test_change_key}; pub use change_key_or_push::test_change_key_or_push; pub use contains::test_contains; pub use key_of::test_key_of; From 5790dbd1d5a9d7db94cdbdce0940a40198b68751 Mon Sep 17 00:00:00 2001 From: orxfun Date: Fri, 28 Aug 2026 14:18:21 +0200 Subject: [PATCH 2/3] fix clippy --- src/dary/heap.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dary/heap.rs b/src/dary/heap.rs index 988d709..1d9a34b 100644 --- a/src/dary/heap.rs +++ b/src/dary/heap.rs @@ -43,8 +43,9 @@ where padded_tree.append(&mut tree); tree = padded_tree; - for position in heap_offset..tree.len() { - positions.insert(&tree[position].0, position); + for (position, (node, _key)) in tree.iter().enumerate().skip(heap_offset) { + // for position in heap_offset..tree.len() { + positions.insert(node, position); } let mut heap = Self { tree, positions }; From 12211af43f1ed169df78aa87d0a75518cf452aca Mon Sep 17 00:00:00 2001 From: orxfun Date: Fri, 28 Aug 2026 15:07:17 +0200 Subject: [PATCH 3/3] add documentation --- src/dary/daryheap_map.rs | 4 ++++ src/dary/heap.rs | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dary/daryheap_map.rs b/src/dary/daryheap_map.rs index c6a0308..f72ccd6 100644 --- a/src/dary/daryheap_map.rs +++ b/src/dary/daryheap_map.rs @@ -138,6 +138,9 @@ where } } +/// Creates a d-ary heap from an iterator in linear time. +/// +/// The iterator must not contain the same node more than once. impl FromIterator<(N, K)> for DaryHeapWithMap where N: Index, @@ -150,6 +153,7 @@ where } } +/// The iterator must not contain the same node more than once. impl DaryHeapWithMap where N: Index, diff --git a/src/dary/heap.rs b/src/dary/heap.rs index 1d9a34b..1fa9d62 100644 --- a/src/dary/heap.rs +++ b/src/dary/heap.rs @@ -44,7 +44,6 @@ where tree = padded_tree; for (position, (node, _key)) in tree.iter().enumerate().skip(heap_offset) { - // for position in heap_offset..tree.len() { positions.insert(node, position); }