iter_opt_filter/
lib.rs

1//! This library adds the [optional_filter] which is provided for all [Iterators][Iterator] by the [IteratorOptionalFilterExt] extension trait.
2//! 
3//! The [optional_filter] takes an `Option<fn(&item) -> bool>`, which allows for easy conditional filtering.
4//! This however comes at a performance cost compared to a normal [filter][Iterator::filter] or the inner [Iterator] by itself.
5//! But it is generally faster than a `Box<dyn Iterator>`.
6//!
7//! See examples on the [optional_filter] method.
8//!
9//! [optional_filter]: IteratorOptionalFilterExt::optional_filter
10
11#![cfg_attr(test, feature(test))]
12
13use std::fmt;
14use std::iter::FusedIterator;
15
16/// Extension trait for adding the [optional_filter][IteratorOptionalFilterExt::optional_filter] method to iterators.
17pub trait IteratorOptionalFilterExt<P: FnMut(&Self::Item) -> bool>: Iterator + Sized {
18    /// Filters the iterator with the predicate, like [filter][Iterator::filter]. If the predicate is `None`, all items will be returned.
19    /// 
20    /// # Examples
21    /// 
22    /// Basic usage:
23    /// 
24    /// ```
25    /// use iter_opt_filter::IteratorOptionalFilterExt;
26    ///
27    /// let mut iter = (0..3).optional_filter(Some(|&item: &usize| item % 2 == 0));
28    /// assert_eq!(iter.next(), Some(0));
29    /// assert_eq!(iter.next(), Some(2));
30    /// assert_eq!(iter.next(), None);
31    /// 
32    /// let mut iter = (0..3).optional_filter(None::<fn(&usize) -> bool>);
33    /// assert_eq!(iter.next(), Some(0));
34    /// assert_eq!(iter.next(), Some(1));
35    /// assert_eq!(iter.next(), Some(2));
36    /// assert_eq!(iter.next(), None);
37    /// ```
38    ///
39    /// Because the type stays the same, regardless of the predicate being `Some` or `None`, the filters are easily chainable:
40    /// 
41    /// ```
42    /// use iter_opt_filter::IteratorOptionalFilterExt;
43    /// 
44    /// let mut iter = (0..3)
45    ///     .optional_filter(Some(|&item: &usize| item % 2 == 0))
46    ///     .optional_filter(None::<fn(&usize) -> bool>)
47    ///     .optional_filter(Some(|&item: &usize| item > 1));
48    /// assert_eq!(iter.next(), Some(2));
49    /// assert_eq!(iter.next(), None);
50    /// ```
51    fn optional_filter(self, predicate: Option<P>) -> OptionalFilter<Self, P> {
52        OptionalFilter {
53            iter: self,
54            predicate,
55        }
56    }
57}
58
59impl<I, P> IteratorOptionalFilterExt<P> for I
60where
61    I: Iterator,
62    P: FnMut(&I::Item) -> bool
63{}
64/// An iterator that optionally filters the items with a predicate.
65/// This `struct` is created by the [optional_filter][IteratorOptionalFilterExt::optional_filter] method provided by the [IteratorOptionalFilterExt] extension trait.
66#[must_use = "iterators are lazy and do nothing unless consumed"]
67#[derive(Clone)]
68pub struct OptionalFilter<I, P> {
69    iter: I,
70    predicate: Option<P>,
71}
72
73impl<I, P> Iterator for OptionalFilter<I, P>
74where
75    I: Iterator,
76    P: FnMut(&I::Item) -> bool
77{
78    type Item = I::Item;
79
80    #[inline]
81    fn next(&mut self) -> Option<Self::Item> {
82        match &mut self.predicate {
83            Some(predicate) => self.iter.find(predicate),
84            None => self.iter.next(),
85        }
86    }
87
88    #[inline]
89    fn size_hint(&self) -> (usize, Option<usize>) {
90        let (lower, upper) = self.iter.size_hint();
91        if self.predicate.is_some() {
92            (0, upper)
93        }
94        else {
95            (lower, upper)
96        }
97    }
98
99    // Specialization from std::iter::Filter
100    #[inline]
101    fn count(self) -> usize {
102        #[inline]
103        fn to_usize<T>(mut predicate: impl FnMut(&T) -> bool) -> impl FnMut(T) -> usize {
104            move |x| predicate(&x) as usize
105        }
106        
107        match self.predicate {
108            Some(predicate) => self.iter.map(to_usize(predicate)).sum(),
109            None => self.iter.count(),
110        }
111    }
112}
113
114impl<I, P> DoubleEndedIterator for OptionalFilter<I, P>
115where
116    I: DoubleEndedIterator,
117    P: FnMut(&I::Item) -> bool
118{
119    fn next_back(&mut self) -> Option<Self::Item> {
120        match &mut self.predicate {
121            Some(predicate) => self.iter.rfind(predicate),
122            None => self.iter.next_back(),
123        }
124    }
125}
126
127impl<I, P> FusedIterator for OptionalFilter<I, P>
128where
129    I: FusedIterator,
130    P: FnMut(&I::Item) -> bool
131{}
132
133impl<I: fmt::Debug, P> fmt::Debug for OptionalFilter<I, P> {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        f.debug_struct("OptionalFilter")
136            .field("iter", &self.iter)
137            .finish()
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::*;
144    extern crate test;
145
146    #[test]
147    fn const_enabled() {
148        let output: Vec<_> = (0..10).optional_filter(Some(|&item: &usize| item % 2 == 0)).collect();
149        assert_eq!(output, vec![0, 2, 4, 6, 8])
150    }
151
152    #[test]
153    fn const_disabled() {
154        let output: Vec<_> = (0..10).optional_filter(None::<fn(&usize) -> bool>).collect();
155        assert_eq!(output, (0..10).collect::<Vec<_>>())
156    }
157
158    #[test]
159    fn double_ended_enabled() {
160        let mut iter = (0..10).optional_filter(Some(|&item: &usize| item % 2 == 0));
161        assert_eq!(iter.next(), Some(0));
162        assert_eq!(iter.next_back(), Some(8));
163        assert_eq!(iter.next_back(), Some(6));
164        assert_eq!(iter.next_back(), Some(4));
165        assert_eq!(iter.next(), Some(2));
166        assert_eq!(iter.next(), None);
167        assert_eq!(iter.next_back(), None);
168    }
169
170    #[test]
171    fn nested_const() {
172        let output: Vec<_> = (0..10)
173            .optional_filter(Some(|&item: &usize| item % 2 == 0))
174            .optional_filter(None::<fn(&usize) -> bool>)
175            .optional_filter(Some(|&item: &usize| item % 3 == 0))
176            .collect();
177        assert_eq!(output, vec![0, 6])
178    }
179
180    // bool_to_option is still unstable
181    fn bool_to_opt<T>(b: bool, some: T) -> Option<T> {
182        if b {
183            Some(some)
184        }
185        else {
186            None
187        }
188    }
189    
190    fn filter_nested(cond1: bool, cond2: bool) -> Vec<usize> {
191        let cond1 = test::black_box(cond1);
192        let cond2 = test::black_box(cond2);
193        (0..10)
194            .optional_filter(bool_to_opt(cond1, |&item: &usize| item % 2 == 0))
195            .optional_filter(bool_to_opt(cond2, |&item: &usize| item % 3 == 0))
196            .collect()
197    }
198
199    #[test]
200    fn nested_false_false() {
201        assert_eq!(filter_nested(false, false), (0..10).collect::<Vec<usize>>());
202    }
203
204    #[test]
205    fn nested_false_true() {
206        assert_eq!(filter_nested(false, true), vec![0, 3, 6, 9]);
207    }
208
209    #[test]
210    fn nested_true_false() {
211        assert_eq!(filter_nested(true, false), vec![0, 2, 4, 6, 8]);
212    }
213
214    #[test]
215    fn nested_true_true() {
216        assert_eq!(filter_nested(true, true), vec![0, 6]);
217    }
218
219    mod benches {
220        use super::*;
221        use test::Bencher;
222
223        trait IteratorOptionalFilterBoxedExt<'a, P: FnMut(&Self::Item) -> bool>
224        where
225            Self: Iterator + Sized + 'a,
226            P: 'a
227        {
228            fn optional_filter_boxed(self, predicate: Option<P>) -> Box<dyn Iterator<Item = Self::Item> + 'a>
229            {
230                match predicate {
231                    Some(predicate) => Box::new(self.filter(predicate)),
232                    None => Box::new(self),
233                }
234            }
235        }
236
237        impl<'a, I, P> IteratorOptionalFilterBoxedExt<'a, P> for I
238        where
239            I: Iterator + 'a,
240            P: FnMut(&I::Item) -> bool + 'a
241        {}
242        
243        mod collect_vec {
244            use super::*;
245            #[bench]
246            fn disabled_baseline(b: &mut Bencher) {
247                b.iter(|| {
248                    let n: usize = test::black_box(1_000_000);
249                    (0..n).collect::<Vec<_>>()
250                })
251            }
252        
253            #[bench]
254            fn disabled_blackbox(b: &mut Bencher) {
255                b.iter(|| {
256                    let n: usize = test::black_box(1_000_000);
257                    let f: Option<fn(&usize) -> bool> = test::black_box(None);
258                    (0..n).optional_filter(f).collect::<Vec<_>>()
259                })
260            }
261        
262            #[bench]
263            fn disabled_blackbox_boxed(b: &mut Bencher) {
264                b.iter(|| {
265                    let n: usize = test::black_box(1_000_000);
266                    let f: Option<fn(&usize) -> bool> = test::black_box(None);
267                    (0..n).optional_filter_boxed(f).collect::<Vec<_>>()
268                })
269            }
270        
271            #[bench]
272            fn disabled_const(b: &mut Bencher) {
273                b.iter(|| {
274                    let n: usize = test::black_box(1_000_000);
275                    (0..n).optional_filter(None::<fn(&usize) -> bool>).collect::<Vec<_>>()
276                })
277            }
278        
279            #[bench]
280            fn disabled_const_boxed(b: &mut Bencher) {
281                b.iter(|| {
282                    let n: usize = test::black_box(1_000_000);
283                    (0..n).optional_filter_boxed(None::<fn(&usize) -> bool>).collect::<Vec<_>>()
284                })
285            }
286        
287            #[bench]
288            fn enabled_baseline(b: &mut Bencher) {
289                b.iter(|| {
290                    let n: usize = test::black_box(1_000_000);
291                    (0..n).filter(|&item| item % 2 == 0).collect::<Vec<_>>()
292                })
293            }
294        
295            #[bench]
296            fn enabled_blackbox(b: &mut Bencher) {
297                b.iter(|| {
298                    let n: usize = test::black_box(1_000_000);
299                    let f: Option<fn(&usize) -> bool> = test::black_box(Some(|&item| item % 2 == 0));
300                    (0..n).optional_filter(f).collect::<Vec<_>>()
301                })
302            }
303        
304            #[bench]
305            fn enabled_blackbox_boxed(b: &mut Bencher) {
306                b.iter(|| {
307                    let n: usize = test::black_box(1_000_000);
308                    let f: Option<fn(&usize) -> bool> = test::black_box(Some(|&item| item % 2 == 0));
309                    (0..n).optional_filter_boxed(f).collect::<Vec<_>>()
310                })
311            }
312        
313            #[bench]
314            fn enabled_const(b: &mut Bencher) {
315                b.iter(|| {
316                    let n: usize = test::black_box(1_000_000);
317                    let f: Option<fn(&usize) -> bool> = Some(|&item| item % 2 == 0);
318                    (0..n).optional_filter(f).collect::<Vec<_>>()
319                })
320            }
321        
322            #[bench]
323            fn enabled_const_boxed(b: &mut Bencher) {
324                b.iter(|| {
325                    let n: usize = test::black_box(1_000_000);
326                    let f: Option<fn(&usize) -> bool> = Some(|&item| item % 2 == 0);
327                    (0..n).optional_filter_boxed(f).collect::<Vec<_>>()
328                })
329            }
330        }
331
332        mod count {
333            use super::*;
334            #[bench]
335            fn disabled_baseline(b: &mut Bencher) {
336                b.iter(|| {
337                    let n: usize = test::black_box(1_000_000);
338                    (0..n).count()
339                })
340            }
341        
342            #[bench]
343            fn disabled_blackbox(b: &mut Bencher) {
344                b.iter(|| {
345                    let n: usize = test::black_box(1_000_000);
346                    let f: Option<fn(&usize) -> bool> = test::black_box(None);
347                    (0..n).optional_filter(f).count()
348                })
349            }
350        
351            #[bench]
352            fn disabled_blackbox_boxed(b: &mut Bencher) {
353                b.iter(|| {
354                    let n: usize = test::black_box(1_000_000);
355                    let f: Option<fn(&usize) -> bool> = test::black_box(None);
356                    (0..n).optional_filter_boxed(f).count()
357                })
358            }
359        
360            #[bench]
361            fn disabled_const(b: &mut Bencher) {
362                b.iter(|| {
363                    let n: usize = test::black_box(1_000_000);
364                    (0..n).optional_filter(None::<fn(&usize) -> bool>).count()
365                })
366            }
367        
368            #[bench]
369            fn disabled_const_boxed(b: &mut Bencher) {
370                b.iter(|| {
371                    let n: usize = test::black_box(1_000_000);
372                    (0..n).optional_filter_boxed(None::<fn(&usize) -> bool>).count()
373                })
374            }
375        
376            #[bench]
377            fn enabled_baseline(b: &mut Bencher) {
378                b.iter(|| {
379                    let n: usize = test::black_box(1_000_000);
380                    (0..n).filter(|&item| item % 2 == 0).count()
381                })
382            }
383        
384            #[bench]
385            fn enabled_blackbox(b: &mut Bencher) {
386                b.iter(|| {
387                    let n: usize = test::black_box(1_000_000);
388                    let f: Option<fn(&usize) -> bool> = test::black_box(Some(|&item| item % 2 == 0));
389                    (0..n).optional_filter(f).count()
390                })
391            }
392        
393            #[bench]
394            fn enabled_blackbox_boxed(b: &mut Bencher) {
395                b.iter(|| {
396                    let n: usize = test::black_box(1_000_000);
397                    let f: Option<fn(&usize) -> bool> = test::black_box(Some(|&item| item % 2 == 0));
398                    (0..n).optional_filter_boxed(f).count()
399                })
400            }
401        
402            #[bench]
403            fn enabled_const(b: &mut Bencher) {
404                b.iter(|| {
405                    let n: usize = test::black_box(1_000_000);
406                    let f: Option<fn(&usize) -> bool> = Some(|&item| item % 2 == 0);
407                    (0..n).optional_filter(f).count()
408                })
409            }
410        
411            #[bench]
412            fn enabled_const_boxed(b: &mut Bencher) {
413                b.iter(|| {
414                    let n: usize = test::black_box(1_000_000);
415                    let f: Option<fn(&usize) -> bool> = Some(|&item| item % 2 == 0);
416                    (0..n).optional_filter_boxed(f).count()
417                })
418            }
419        }
420
421        mod sum {
422            use super::*;
423            #[bench]
424            fn disabled_baseline(b: &mut Bencher) {
425                b.iter(|| {
426                    let n: usize = test::black_box(1_000_000);
427                    (0..n).sum::<usize>()
428                })
429            }
430        
431            #[bench]
432            fn disabled_blackbox(b: &mut Bencher) {
433                b.iter(|| {
434                    let n: usize = test::black_box(1_000_000);
435                    let f: Option<fn(&usize) -> bool> = test::black_box(None);
436                    (0..n).optional_filter(f).sum::<usize>()
437                })
438            }
439        
440            #[bench]
441            fn disabled_blackbox_boxed(b: &mut Bencher) {
442                b.iter(|| {
443                    let n: usize = test::black_box(1_000_000);
444                    let f: Option<fn(&usize) -> bool> = test::black_box(None);
445                    (0..n).optional_filter_boxed(f).sum::<usize>()
446                })
447            }
448        
449            #[bench]
450            fn disabled_const(b: &mut Bencher) {
451                b.iter(|| {
452                    let n: usize = test::black_box(1_000_000);
453                    (0..n).optional_filter(None::<fn(&usize) -> bool>).sum::<usize>()
454                })
455            }
456        
457            #[bench]
458            fn disabled_const_boxed(b: &mut Bencher) {
459                b.iter(|| {
460                    let n: usize = test::black_box(1_000_000);
461                    (0..n).optional_filter_boxed(None::<fn(&usize) -> bool>).sum::<usize>()
462                })
463            }
464        
465            #[bench]
466            fn enabled_baseline(b: &mut Bencher) {
467                b.iter(|| {
468                    let n: usize = test::black_box(1_000_000);
469                    (0..n).filter(|&item| item % 2 == 0).sum::<usize>()
470                })
471            }
472        
473            #[bench]
474            fn enabled_blackbox(b: &mut Bencher) {
475                b.iter(|| {
476                    let n: usize = test::black_box(1_000_000);
477                    let f: Option<fn(&usize) -> bool> = test::black_box(Some(|&item| item % 2 == 0));
478                    (0..n).optional_filter(f).sum::<usize>()
479                })
480            }
481        
482            #[bench]
483            fn enabled_blackbox_boxed(b: &mut Bencher) {
484                b.iter(|| {
485                    let n: usize = test::black_box(1_000_000);
486                    let f: Option<fn(&usize) -> bool> = test::black_box(Some(|&item| item % 2 == 0));
487                    (0..n).optional_filter_boxed(f).sum::<usize>()
488                })
489            }
490        
491            #[bench]
492            fn enabled_const(b: &mut Bencher) {
493                b.iter(|| {
494                    let n: usize = test::black_box(1_000_000);
495                    let f: Option<fn(&usize) -> bool> = Some(|&item| item % 2 == 0);
496                    (0..n).optional_filter(f).sum::<usize>()
497                })
498            }
499        
500            #[bench]
501            fn enabled_const_boxed(b: &mut Bencher) {
502                b.iter(|| {
503                    let n: usize = test::black_box(1_000_000);
504                    let f: Option<fn(&usize) -> bool> = Some(|&item| item % 2 == 0);
505                    (0..n).optional_filter_boxed(f).sum::<usize>()
506                })
507            }
508        }
509    }
510}