Skip to main content

range_set_blaze/
sym_diff_iter_map.rs

1use core::{
2    cmp::{self, min},
3    iter::FusedIterator,
4    ops::RangeInclusive,
5};
6
7use alloc::collections::BinaryHeap;
8
9use crate::{
10    Integer, MergeMap, SortedDisjointMap, SymDiffKMergeMap, SymDiffMergeMap,
11    map::ValueRef,
12    merge_map::KMergeMap,
13    sorted_disjoint_map::{Priority, PrioritySortedStartsMap},
14};
15
16/// This `struct` is created by the [`symmetric_difference`] method on [`SortedDisjointMap`]. See [`symmetric_difference`]'s
17/// documentation for more.
18///
19/// [`SortedDisjointMap`]: crate::SortedDisjointMap
20/// [`symmetric_difference`]: crate::SortedDisjointMap::symmetric_difference
21#[derive(Clone, Debug)]
22#[must_use = "iterators are lazy and do nothing unless consumed"]
23pub struct SymDiffIterMap<T, VR, I> {
24    iter: I,
25    next_item: Option<Priority<T, VR>>,
26    workspace: BinaryHeap<Priority<T, VR>>,
27    workspace_next_end: Option<T>,
28    gather: Option<(RangeInclusive<T>, VR)>,
29    ready_to_go: Option<(RangeInclusive<T>, VR)>,
30}
31
32#[expect(clippy::ref_option)]
33fn min_next_end<T: Integer>(next_end: &Option<T>, next_item_end: T) -> T {
34    next_end.map_or_else(
35        || next_item_end,
36        |current_end| cmp::min(current_end, next_item_end),
37    )
38}
39
40impl<T, VR, I> FusedIterator for SymDiffIterMap<T, VR, I>
41where
42    T: Integer,
43    VR: ValueRef,
44    I: PrioritySortedStartsMap<T, VR>,
45{
46}
47
48impl<T, VR, I> Iterator for SymDiffIterMap<T, VR, I>
49where
50    T: Integer,
51    VR: ValueRef,
52    I: PrioritySortedStartsMap<T, VR>,
53{
54    type Item = (RangeInclusive<T>, VR);
55
56    fn next(&mut self) -> Option<(RangeInclusive<T>, VR)> {
57        // Keep doing this until we have something to return.
58        loop {
59            if let Some(value) = self.ready_to_go.take() {
60                // If ready_to_go is Some, return the value immediately.
61                return Some(value);
62            }
63
64            // if self.next_item should go into the workspace, then put it there, get the next, next_item, and loop
65            if let Some(next_item) = self.next_item.take() {
66                let (next_start, next_end) = next_item.start_and_end();
67
68                // If workspace is empty, just push the next item
69                let Some(best) = self.workspace.peek() else {
70                    self.workspace_next_end =
71                        Some(min_next_end(&self.workspace_next_end, next_end));
72                    self.workspace.push(next_item);
73                    self.next_item = self.iter.next();
74                    continue; // return to top of the main processing loop
75                };
76                let best = best.range_value();
77                if next_start == *best.0.start() {
78                    // Always push (this differs from UnionIterMap)
79                    self.workspace_next_end =
80                        Some(min_next_end(&self.workspace_next_end, next_end));
81                    self.workspace.push(next_item);
82                    self.next_item = self.iter.next();
83                    continue; // return to top of the main processing loop
84                }
85
86                // It does not go into the workspace, so just hold it and keep processing.
87                self.next_item = Some(next_item);
88            }
89
90            // If the workspace is empty, we are done.
91            let Some(best) = self.workspace.peek() else {
92                debug_assert!(self.next_item.is_none());
93                debug_assert!(self.ready_to_go.is_none());
94                return self.gather.take();
95            };
96            let best = best.range_value();
97
98            // We buffer for output the best item up to the start of the next item (if any).
99
100            // Find the start of the next item, if any.
101            let mut next_end = self
102                .workspace_next_end
103                .take()
104                .expect("Real Assert: safe because we know the workspace is not empty");
105            if let Some(next_item) = self.next_item.as_ref() {
106                next_end = min(next_item.start().sub_one(), next_end);
107            }
108
109            // Add the front of best to the gather buffer.
110            if let Some(mut gather) = self.gather.take() {
111                if gather.1.borrow() == best.1.borrow()
112                    && (*gather.0.end()).add_one() == *best.0.start()
113                {
114                    if self.workspace.len().is_odd() {
115                        // if the gather is contiguous with the best, then merge them
116                        gather.0 = *gather.0.start()..=next_end;
117                        self.gather = Some(gather);
118                    } else {
119                        // if an even number of items in the workspace, then flush the gather
120                        self.ready_to_go = Some(gather);
121                        debug_assert!(self.gather.is_none());
122                    }
123                } else {
124                    // if the gather is not contiguous with the best, then output the gather and set the gather to the best
125                    self.ready_to_go = Some(gather);
126                    // FYI: this code appear twice # 1 of 2
127                    if self.workspace.len().is_odd() {
128                        self.gather = Some((*best.0.start()..=next_end, best.1.clone()));
129                    } else {
130                        debug_assert!(self.gather.is_none());
131                    }
132                }
133            } else {
134                // if there is no gather, then set the gather to the best
135                // FYI: this code appear twice # 2 of 2
136                if self.workspace.len().is_odd() {
137                    self.gather = Some((*best.0.start()..=next_end, best.1.clone()));
138                } else {
139                    debug_assert!(self.gather.is_none());
140                }
141            }
142
143            // We also update the workspace to removing any items that are completely covered by the new_start.
144            // (Unlike UnionIterMap, we must keep any items that have a lower priority and are shorter than the new best.)
145            self.workspace_next_end = None;
146            self.workspace = self
147                .workspace
148                .drain()
149                .filter(|item| item.end() > next_end)
150                .map(|mut item| {
151                    item.set_range(next_end.add_one()..=item.end());
152                    self.workspace_next_end =
153                        Some(min_next_end(&self.workspace_next_end, item.end()));
154                    item
155                })
156                .collect();
157        } // end of main loop
158    }
159}
160
161impl<T, VR, L, R> SymDiffMergeMap<T, VR, L, R>
162where
163    T: Integer,
164    VR: ValueRef,
165    L: SortedDisjointMap<T, VR>,
166    R: SortedDisjointMap<T, VR>,
167{
168    #[inline]
169    pub(crate) fn new2(left: L, right: R) -> Self {
170        let iter = MergeMap::new(left, right);
171        Self::new(iter)
172    }
173}
174
175impl<T, VR, J> SymDiffKMergeMap<T, VR, J>
176where
177    T: Integer,
178    VR: ValueRef,
179    J: SortedDisjointMap<T, VR>,
180{
181    #[inline]
182    pub(crate) fn new_k<K>(k: K) -> Self
183    where
184        K: IntoIterator<Item = J>,
185    {
186        let iter = KMergeMap::new(k);
187        Self::new(iter)
188    }
189}
190
191impl<T, VR, I> SymDiffIterMap<T, VR, I>
192where
193    T: Integer,
194    VR: ValueRef,
195    I: PrioritySortedStartsMap<T, VR>,
196{
197    #[inline]
198    pub(crate) fn new(mut iter: I) -> Self {
199        let item = iter.next();
200        Self {
201            iter,
202            next_item: item,
203            workspace: BinaryHeap::new(),
204            workspace_next_end: None,
205            gather: None,
206            ready_to_go: None,
207        }
208    }
209}
210
211#[allow(clippy::wrong_self_convention)]
212#[allow(clippy::redundant_pub_crate)]
213pub(crate) trait UsizeExtensions {
214    fn is_odd(self) -> bool;
215}
216
217impl UsizeExtensions for usize {
218    #[inline]
219    fn is_odd(self) -> bool {
220        self & 1 != 0
221    }
222}