Skip to main content

fluent_iterable/ops/
combining.rs

1//! Adapters for the combining operations.
2
3use std::collections::HashMap;
4use std::hash::Hash;
5
6/// Inner equi-join produced by [`FluentIter::combine`] — see there.
7///
8/// Lazy: the right side is indexed on the first `next()` (that side is read
9/// in full, like the JS side indexes it too), then the left side streams —
10/// `take(k)` stops the left walk once `k` pairs are out.
11pub struct Combine<I, J, K, KL, KR>
12where
13    I: Iterator,
14    J: Iterator,
15{
16    pub(crate) left: I,
17    pub(crate) key_left: KL,
18    pub(crate) right: J,
19    pub(crate) key_right: KR,
20    pub(crate) indexed: bool,
21    pub(crate) idx: HashMap<K, Vec<J::Item>>,
22    /// Left item currently yielding pairs, its key (cached: the closure is
23    /// not re-invoked per pair), and the next bucket position.
24    pub(crate) pending: Option<(I::Item, K, usize)>,
25}
26
27impl<I, J, K, KL, KR> Iterator for Combine<I, J, K, KL, KR>
28where
29    I: Iterator,
30    J: Iterator,
31    K: Eq + Hash + Clone,
32    KL: FnMut(&I::Item) -> K,
33    KR: FnMut(&J::Item) -> K,
34    I::Item: Clone,
35    J::Item: Clone,
36{
37    type Item = (I::Item, J::Item);
38
39    fn next(&mut self) -> Option<Self::Item> {
40        if !self.indexed {
41            for r in self.right.by_ref() {
42                self.idx.entry((self.key_right)(&r)).or_default().push(r);
43            }
44            self.indexed = true;
45        }
46        loop {
47            if self.pending.is_none() {
48                // Advance the left walk until an element finds a bucket.
49                loop {
50                    let l = self.left.next()?;
51                    let k = (self.key_left)(&l);
52                    if self.idx.get(&k).is_some_and(|b| !b.is_empty()) {
53                        self.pending = Some((l, k, 0));
54                        break;
55                    }
56                }
57            }
58            let (left, key, pos) = self.pending.take().expect("pending was just set");
59            let bucket = &self.idx[&key];
60            if pos < bucket.len() {
61                let pair = (left.clone(), bucket[pos].clone());
62                self.pending = Some((left, key, pos + 1));
63                return Some(pair);
64            }
65            // bucket exhausted — the outer loop takes the next matching left
66        }
67    }
68}