use std::collections::HashMap;
#[derive(Debug)]
pub(crate) struct ReorderByIndex<I, T>
where
I: Iterator<Item = (usize, T)>,
{
iter: I,
next: usize,
buffer: HashMap<usize, T>,
exhausted: bool,
}
impl<I, T> ReorderByIndex<I, T>
where
I: Iterator<Item = (usize, T)>,
{
pub(crate) fn new(iter: I) -> Self {
Self::with_start(iter, 0)
}
pub(crate) fn with_start(iter: I, start: usize) -> Self {
Self {
iter,
next: start,
buffer: HashMap::new(),
exhausted: false,
}
}
}
impl<I, T> Iterator for ReorderByIndex<I, T>
where
I: Iterator<Item = (usize, T)>,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(item) = self.buffer.remove(&self.next) {
self.next += 1;
return Some(item);
}
if self.exhausted {
let min_idx = *self.buffer.keys().min()?;
self.next = min_idx;
continue;
}
match self.iter.next() {
Some((idx, item)) => {
if idx == self.next {
self.next += 1;
return Some(item);
}
self.buffer.insert(idx, item);
}
None => self.exhausted = true,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reorders_out_of_order_items() {
let iter = vec![(1, 20), (0, 10)].into_iter();
let mut ordered = ReorderByIndex::new(iter);
assert_eq!(ordered.next(), Some(10));
assert_eq!(ordered.next(), Some(20));
assert_eq!(ordered.next(), None);
}
#[test]
fn with_start_reorders_from_given_index() {
let src = vec![(2, "a"), (4, "c"), (3, "b")];
let out: Vec<_> = ReorderByIndex::with_start(src.into_iter(), 2).collect();
assert_eq!(out, vec!["a", "b", "c"]);
}
#[test]
fn handles_already_ordered_items() {
let items = vec![(0, 'a'), (1, 'b'), (2, 'c')];
let out: Vec<_> = ReorderByIndex::new(items.into_iter()).collect();
assert_eq!(out, vec!['a', 'b', 'c']);
}
#[test]
fn handles_reverse_order() {
let items = vec![(2, 'c'), (1, 'b'), (0, 'a')];
let out: Vec<_> = ReorderByIndex::new(items.into_iter()).collect();
assert_eq!(out, vec!['a', 'b', 'c']);
}
#[test]
fn handles_empty_iterator() {
let items: Vec<(usize, i32)> = vec![];
let out: Vec<_> = ReorderByIndex::new(items.into_iter()).collect();
assert!(out.is_empty());
}
#[test]
fn handles_gap_in_indices() {
let items = vec![(0, 'a'), (2, 'c'), (3, 'd')];
let out: Vec<_> = ReorderByIndex::new(items.into_iter()).collect();
assert_eq!(out, vec!['a', 'c', 'd']);
}
#[test]
fn handles_multiple_gaps() {
let items = vec![(0, 'a'), (2, 'c'), (4, 'e')];
let out: Vec<_> = ReorderByIndex::new(items.into_iter()).collect();
assert_eq!(out, vec!['a', 'c', 'e']);
}
#[test]
fn handles_gap_at_start() {
let items = vec![(1, 'b'), (2, 'c')];
let out: Vec<_> = ReorderByIndex::new(items.into_iter()).collect();
assert_eq!(out, vec!['b', 'c']);
}
#[test]
fn handles_single_item_after_gap() {
let items = vec![(5, 'f')];
let out: Vec<_> = ReorderByIndex::new(items.into_iter()).collect();
assert_eq!(out, vec!['f']);
}
}