window_sort_iterator/lib.rs
1//! An iterator adapter that sorts items within a sliding window.
2//!
3//! This applies a sliding window over the iterator, sorts items within that window, and yields them
4//! in order within that window.
5//! The implementation uses a [std::collections::BinaryHeap], so the default direction
6//! is from highest to lowest item.
7//! In order to change the sorting direction, use [std::cmp::Reverse].
8//!
9//! This is useful in situations where you, e.g., have an of items which are mostly sorted which is
10//! possibly large, so you don't want to keep it in memory to be sorted.
11//! If you can define some reasonable bounds on the "scrambledness" of the iterator, you can then
12//! un-scramble it with this.
13//!
14//! Internally, the algorithm tries to keep the heap filled as long as the underlying iterator
15//! produces items.
16//! When the heap is filled or the underlying iterator does not yield anymore items, the next item
17//! is popped off the heap.
18//!
19//! # Examples
20//!
21//! Basic usage: Adapt an iterator to be sorted.
22//! ```
23//! use window_sort_iterator::WindowSortIterExt;
24//!
25//! let a = &[4, 2, 3, 1];
26//! let mut it = a.iter().cloned().window_sort(2);
27//! assert_eq!(Some(4), it.next());
28//! assert_eq!(Some(3), it.next());
29//! assert_eq!(Some(2), it.next());
30//! assert_eq!(Some(1), it.next());
31//! assert_eq!(None, it.next());
32//! ```
33//!
34//! Reverse, to use a min-heap:
35//! ```
36//! use std::cmp::Reverse;
37//! use window_sort_iterator::window_sort;
38//!
39//! let a = &[1, 4, 2, 3];
40//! let mut it = window_sort(a.iter().cloned().map(|i| Reverse(i)), 2).map(|i| i.0);
41//! assert_eq!(Some(1), it.next());
42//! assert_eq!(Some(2), it.next());
43//! assert_eq!(Some(3), it.next());
44//! assert_eq!(Some(4), it.next());
45//! assert_eq!(None, it.next())
46//! ```
47
48use std::collections::BinaryHeap;
49
50/// An iterator adapter that sorts items within a sliding window.
51/// See the crate-level documentation for more info.
52pub struct WindowSort<I>
53where
54 I: Iterator,
55 <I as Iterator>::Item: Ord,
56{
57 orig: I,
58 window_size: usize,
59 heap: BinaryHeap<I::Item>,
60}
61
62impl<I> Iterator for WindowSort<I>
63where
64 I: Iterator,
65 <I as Iterator>::Item: Ord,
66{
67 type Item = I::Item;
68
69 #[inline]
70 fn next(&mut self) -> Option<Self::Item> {
71 // Do we need to fill up the heap?
72 while self.heap.len() < self.window_size {
73 // Are there still items to be read from the underlying iterator?
74 if let Some(item) = self.orig.next() {
75 // If yes: push onto the heap.
76 self.heap.push(item);
77 } else {
78 // If not: break from filling the heap, pop highest item.
79 break;
80 }
81 }
82
83 // Pop highest item off the heap.
84 // If the heap is empty this will return None.
85 self.heap.pop()
86 }
87
88 #[inline]
89 fn size_hint(&self) -> (usize, Option<usize>) {
90 let heap_items = self.heap.len();
91 match self.orig.size_hint() {
92 (lower, Some(upper)) => (
93 lower.saturating_add(heap_items),
94 Some(upper.saturating_add(heap_items)),
95 ),
96 (lower, None) => (lower.saturating_add(heap_items), None),
97 }
98 }
99}
100
101/// Sorts the underlying iterator within a sliding window.
102/// See the crate-level documentation for more info.
103pub fn window_sort<I: Iterator>(xs: I, window_size: usize) -> WindowSort<I>
104where
105 <I as Iterator>::Item: Ord,
106{
107 WindowSort {
108 orig: xs,
109 window_size,
110 heap: BinaryHeap::new(),
111 }
112}
113
114/// Trait that extends iterators with functionality to sort items within a sliding window.
115/// See the crate-level documentation for more info.
116pub trait WindowSortIterExt: Sized {
117 fn window_sort(self, window_size: usize) -> WindowSort<Self>
118 where
119 Self: Iterator,
120 <Self as Iterator>::Item: Ord;
121}
122
123impl<I: Iterator> WindowSortIterExt for I
124where
125 <I as Iterator>::Item: Ord,
126{
127 /// Sorts the underlying iterator within a sliding window.
128 /// See the crate-level documentation for more info.
129 fn window_sort(self, window_size: usize) -> WindowSort<Self> {
130 window_sort(self, window_size)
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn should_sort_i32_fn() {
140 let a = &[3_i32, 4, 2, 1];
141 let mut it = window_sort(a.iter().cloned(), 2);
142 assert_eq!(Some(4), it.next());
143 assert_eq!(Some(3), it.next());
144 assert_eq!(Some(2), it.next());
145 assert_eq!(Some(1), it.next());
146 assert_eq!(None, it.next());
147 }
148
149 #[test]
150 fn should_sort_i32_method() {
151 let a = &[3_i32, 4, 2, 1];
152 let mut it = a.iter().cloned().window_sort(2);
153 assert_eq!(Some(4), it.next());
154 assert_eq!(Some(3), it.next());
155 assert_eq!(Some(2), it.next());
156 assert_eq!(Some(1), it.next());
157 assert_eq!(None, it.next());
158 }
159
160 #[test]
161 fn should_sort_window_only() {
162 let a = &[4_i32, 2, 1, 3];
163 let mut it = window_sort(a.iter().cloned(), 2);
164 assert_eq!(Some(4), it.next());
165 assert_eq!(Some(2), it.next());
166 assert_eq!(Some(3), it.next());
167 assert_eq!(Some(1), it.next());
168 assert_eq!(None, it.next());
169 }
170
171 #[test]
172 fn small_underlying_iterator() {
173 let a = &[2_i32, 3, 4, 1];
174 let mut it = window_sort(a.iter().cloned(), 10);
175 assert_eq!(Some(4), it.next());
176 assert_eq!(Some(3), it.next());
177 assert_eq!(Some(2), it.next());
178 assert_eq!(Some(1), it.next());
179 assert_eq!(None, it.next());
180 }
181}