Skip to content

Commit 58c0362

Browse files
committed
Overhauling heap con/destruction process
1 parent 6551ccd commit 58c0362

File tree

1 file changed

+44
-47
lines changed

1 file changed

+44
-47
lines changed

src/k_smallest.rs

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use alloc::vec::IntoIter;
22
use core::cmp::{Ord, Ordering, Reverse};
33
use core::mem::{replace, transmute, MaybeUninit};
4-
4+
use core::ops::Range;
55

66
fn k_smallest_dynamic<T, I: Iterator<Item = T>>(
77
iter: I,
@@ -10,8 +10,8 @@ fn k_smallest_dynamic<T, I: Iterator<Item = T>>(
1010
) -> IntoIter<T> {
1111
let mut storage = Vec::new();
1212
storage.resize_with(k, MaybeUninit::uninit);
13-
let num_elements = capped_heapsort(iter, &mut storage, order);
14-
storage.truncate(num_elements);
13+
let Range { end, .. } = capped_heapsort(iter, &mut storage, order);
14+
storage.truncate(end);
1515
let initialized: Vec<_> = unsafe { transmute(storage) };
1616
initialized.into_iter()
1717
}
@@ -61,15 +61,22 @@ where
6161
}
6262

6363
/// Consumes a given iterator, leaving the minimum elements in the provided storage in **ascending** order.
64-
/// Returns the number of elements processed, up to the size of the storage
64+
/// Returns the range of initialized elements
6565
fn capped_heapsort<T, I: Iterator<Item = T>>(
6666
iter: I,
6767
storage: &mut [MaybeUninit<T>],
6868
order: impl Fn(&T, &T) -> Ordering,
69-
) -> usize {
70-
let mut heap = MaxHeap::new(&mut storage[..], order);
71-
heap.extend(iter);
72-
heap.total_sort()
69+
) -> Range<usize> {
70+
if storage.is_empty() {
71+
return 0..0;
72+
}
73+
let mut heap = MaxHeap::from_iter(storage, order, iter);
74+
75+
let valid_elements = 0..heap.len;
76+
while heap.len > 0 {
77+
heap.pop();
78+
}
79+
valid_elements
7380
}
7481

7582
/// An efficient heapsort requires that the heap ordering is the inverse of the desired sort order
@@ -83,6 +90,7 @@ fn capped_heapsort<T, I: Iterator<Item = T>>(
8390
struct MaxHeap<'a, T, C> {
8491
// It may be not be possible to shrink the storage for smaller sequencess
8592
// so manually manage the initialization
93+
// This is **assumed not to be empty**
8694
storage: &'a mut [MaybeUninit<T>],
8795
comparator: C,
8896
// SAFETY: this must always be less or equal to the count of actually initialized elements
@@ -93,37 +101,31 @@ impl<'a, T, C> MaxHeap<'a, T, C>
93101
where
94102
C: Fn(&T, &T) -> Ordering,
95103
{
96-
fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
97-
let mut iter = iter.into_iter();
98-
99-
for initial_item in iter.by_ref().take(self.storage.len() - self.len) {
100-
// This is the only point where the length is increased
101-
// And the element we are increasing from is always initialized
102-
self.storage[self.len] = MaybeUninit::new(initial_item);
103-
self.len += 1;
104+
fn from_iter<I>(storage: &'a mut [MaybeUninit<T>], comparator: C, mut iter: I) -> Self
105+
where
106+
I: Iterator<Item = T>,
107+
{
108+
let mut heap = Self {
109+
storage,
110+
comparator,
111+
len: 0,
112+
};
113+
for (i, initial_item) in iter.by_ref().take(heap.storage.len()).enumerate() {
114+
heap.storage[i] = MaybeUninit::new(initial_item);
115+
heap.len += 1;
104116
}
105117
// Filling up the storage and only afterwards rearranging to form a valid heap is slightly more efficient
106118
// (But only by a factor of lg(k) and I'd love to hear of a usecase where that matters)
107-
self.heapify();
119+
heap.heapify();
108120

109-
if self.len == self.storage.len() {
121+
if heap.len == heap.storage.len() {
110122
// Nothing else needs done if we didn't fill the storage in the first place
111123
// Also avoids unexpected behaviour with restartable iterators
112124
for val in iter {
113-
let _ = self.push_pop(val);
125+
let _ = heap.push_pop(val);
114126
}
115127
}
116-
}
117-
}
118-
119-
impl<'a, T, C> MaxHeap<'a, T, C> {
120-
/// Creates an empty [`MaxHeap<T, C>`].
121-
fn new(storage: &'a mut [MaybeUninit<T>], comparator: C) -> Self {
122-
Self {
123-
storage,
124-
comparator,
125-
len: 0,
126-
}
128+
heap
127129
}
128130

129131
/// Retrieves the element at the given index.
@@ -169,9 +171,21 @@ impl<'a, T, C> MaxHeap<'a, T, C> {
169171
}
170172
}
171173

174+
/// Pop the greatest element by putting it at the back of the storage
175+
/// shrinking the heap by 1, and reordering if needed
176+
fn pop(&mut self) {
177+
debug_assert!(self.len > 0);
178+
self.storage.swap(0, self.len - 1);
179+
// Leaves the length shorter than the number of initialized elements
180+
// so that sifting does not disturb already popped elements
181+
self.len -= 1;
182+
self.sift_down(0);
183+
}
184+
172185
/// Insert the given element into the heap without changing its size
173186
/// The displaced element is returned, i.e. either the input or previous max
174187
fn push_pop(&mut self, val: T) -> Option<T> {
188+
if self.compare(self.get(0), Some(&val)) == Some(Ordering::Greater) {
175189
let out = replace(&mut self.storage[0], MaybeUninit::new(val));
176190
self.sift_down(0);
177191
// SAFETY: This has been moved out of storage[0]
@@ -195,23 +209,6 @@ impl<'a, T, C> MaxHeap<'a, T, C> {
195209
fn compare(&self, a: Option<&T>, b: Option<&T>) -> Option<Ordering> {
196210
(self.comparator)(a?, b?).into()
197211
}
198-
199-
/// Heapsorts the storage into **ascending** order by repeatedly popping.
200-
/// The number of elements in the heap is returned.
201-
fn total_sort(mut self) -> usize
202-
where
203-
C: Fn(&T, &T) -> Ordering,
204-
{
205-
let original_len = self.len;
206-
while self.len > 1 {
207-
self.storage.swap(0, self.len - 1);
208-
// Leaves the length shorter than the number of initialized elements
209-
// so that sifting does not disturb already popped elements
210-
self.len -= 1;
211-
self.sift_down(0);
212-
}
213-
original_len
214-
}
215212
}
216213

217214
struct Pair<K, T>(K, T);

0 commit comments

Comments
 (0)