Skip to content

Commit 88395e7

Browse files
committed
Unbundling heap type and adjusting sift_down
The type was not providing much benefit after inlining the other functions
1 parent 409de50 commit 88395e7

File tree

1 file changed

+41
-69
lines changed

1 file changed

+41
-69
lines changed

src/k_smallest.rs

Lines changed: 41 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,44 @@ use core::cmp::{Ord, Ordering, Reverse};
55
fn k_smallest_general<T, I: Iterator<Item = T>>(
66
mut iter: I,
77
k: usize,
8-
comparator: impl Fn(&T, &T) -> Ordering,
8+
mut comparator: impl FnMut(&T, &T) -> Ordering,
99
) -> IntoIter<T> {
1010
if k == 0 {
1111
return Vec::new().into_iter();
1212
}
1313
let mut storage: Vec<T> = iter.by_ref().take(k).collect();
14-
15-
let mut heap = MaxHeap {
16-
len: storage.len(),
17-
storage: &mut storage[..],
18-
comparator,
19-
};
14+
let mut heap = &mut storage[..];
2015

2116
// Rearrange the into a valid heap by reordering from the second-bottom-most layer up
2217
// Slightly faster than ordering on each insert
2318
// (But only by a factor of lg(k) and I'd love to hear of a use case where that matters)
24-
for i in (0..(heap.len / 2 + 1)).rev() {
25-
heap.sift_down(i);
19+
for i in (0..(heap.len() / 2 + 1)).rev() {
20+
sift_down(heap, &mut comparator, i);
2621
}
2722

28-
if k == heap.storage.len() {
23+
if k == heap.len() {
2924
// Nothing else needs done if we didn't fill the storage in the first place
3025
// Also avoids unexpected behaviour with restartable iterators
3126
for val in iter {
32-
if (heap.comparator)(&heap.storage[0], &val) == Ordering::Greater {
33-
heap.storage[0] = val;
34-
heap.sift_down(0);
27+
if comparator(&heap[0], &val) == Ordering::Greater {
28+
heap[0] = val;
29+
sift_down(heap, &mut comparator, 0);
3530
}
3631
}
3732
}
3833

39-
while heap.len > 1 {
40-
heap.storage.swap(0, heap.len - 1);
41-
// Leaves the length shorter than the number of initialized elements
34+
while heap.len() > 1 {
35+
let last_idx = heap.len() - 1;
36+
heap.swap(0, last_idx);
37+
// Leaves the length shorter than the number of elements
4238
// so that sifting does not disturb already popped elements
43-
heap.len -= 1;
44-
heap.sift_down(0);
39+
heap = &mut heap[..last_idx];
40+
sift_down(heap, &mut comparator, 0);
4541
}
4642

4743
storage.into_iter()
4844
}
4945

50-
5146
pub(crate) fn reverse_cmp<T, F>(cmp: F) -> impl Fn(&T, &T) -> Ordering
5247
where
5348
F: Fn(&T, &T) -> Ordering,
@@ -92,59 +87,36 @@ where
9287
results.into_iter()
9388
}
9489

95-
/// An efficient heapsort requires that the heap ordering is the inverse of the desired sort order
96-
/// So the basic case of retrieving minimum elements requires a max heap
97-
///
98-
/// This type does not attempt to reproduce all the functionality of [std::collections::BinaryHeap] and instead only implements what is needed for iter operations,
99-
/// e.g. we do not need to insert single elements.
100-
/// Additionally, some minor optimizations used in the std BinaryHeap are not used here, e.g. elements are actually swapped rather than managing a "hole"
101-
struct MaxHeap<'a, T, C> {
102-
// It may be not be possible to shrink the storage for smaller sequencess
103-
// so manually manage the initialization
104-
// This is **assumed not to be empty**
105-
storage: &'a mut [T],
106-
comparator: C,
107-
// this is always less or equal to the count of actual elements
108-
// allowing it to be less means the heap property can cover only a subset of the vec
109-
// while reusing the storage
110-
len: usize,
111-
}
90+
/// Sift the element currently at `origin` **away** from the root until it is properly ordered
91+
fn sift_down<T>(
92+
heap: &mut [T],
93+
comparator: &mut impl FnMut(&T, &T) -> Ordering,
94+
mut origin: usize,
95+
) {
96+
fn children_of(n: usize) -> (usize, usize) {
97+
(2 * n + 1, 2 * n + 2)
98+
}
11299

113-
impl<'a, T, C> MaxHeap<'a, T, C>
114-
where
115-
C: FnMut(&T, &T) -> Ordering,
116-
{
117-
/// Sift the element currently at `origin` **away** from the root until it is properly ordered
118-
fn sift_down(&mut self, mut origin: usize) {
119-
fn children_of(n: usize) -> (usize, usize) {
120-
(2 * n + 1, 2 * n + 2)
100+
while origin < heap.len() {
101+
let (left_idx, right_idx) = children_of(origin);
102+
if left_idx >= heap.len() {
103+
return;
121104
}
122105

123-
while origin < self.len {
124-
let (left_idx, right_idx) = children_of(origin);
125-
126-
if left_idx >= self.len {
127-
// the left is the earlier child, so if it doesn't exist there's nothing to swap with
128-
return;
129-
}
130-
131-
let items = &self.storage;
132-
133-
let replacement_idx = if right_idx < self.len
134-
&& Ordering::Less == (self.comparator)(&items[left_idx], &items[right_idx])
135-
{
136-
right_idx
137-
} else {
138-
left_idx
139-
};
140-
141-
let cmp = (self.comparator)(&items[origin], &items[replacement_idx]);
142-
if Ordering::Less == cmp {
143-
self.storage.swap(origin, replacement_idx);
144-
origin = replacement_idx;
145-
} else {
146-
return;
147-
}
106+
let replacement_idx = if right_idx < heap.len()
107+
&& Ordering::Less == comparator(&heap[left_idx], &heap[right_idx])
108+
{
109+
right_idx
110+
} else {
111+
left_idx
112+
};
113+
114+
let cmp = comparator(&heap[origin], &heap[replacement_idx]);
115+
if Ordering::Less == cmp {
116+
heap.swap(origin, replacement_idx);
117+
origin = replacement_idx;
118+
} else {
119+
return;
148120
}
149121
}
150122
}

0 commit comments

Comments
 (0)