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}