tea_map/
valid_iter.rs

1use std::fmt::Debug;
2
3use tea_core::prelude::*;
4
5#[derive(Clone, Debug)]
6pub enum Keep {
7    First,
8    Last,
9}
10
11pub trait MapValidBasic<T: IsNone>: TrustedLen<Item = T> + Sized {
12    /// Computes the absolute value of each element in the iterator, ignoring None values.
13    ///
14    /// This method is similar to `abs()`, but it can handle None values.
15    ///
16    /// # Examples
17    ///
18    /// ```
19    /// use tea_core::prelude::*;
20    /// use tea_map::MapValidBasic;
21    ///
22    /// let v = vec![Some(-1), None, Some(2), Some(-3)];
23    /// let result: Vec<_> = v.titer().vabs().collect();
24    /// assert_eq!(result, vec![Some(1), None, Some(2), Some(3)]);
25    /// ```
26    ///
27    /// See also: [`abs()`](crate::MapBasic::abs)
28    #[inline]
29    fn vabs(self) -> impl TrustedLen<Item = T>
30    where
31        T::Inner: Number,
32    {
33        self.map(|v| v.vabs())
34    }
35
36    /// Forward fill values where the mask is true, ignoring None values.
37    ///
38    /// # Arguments
39    ///
40    /// * `mask_func` - A function that returns true for values that should be filled.
41    /// * `value` - An optional value to fill if head values are still None after forward fill.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use tea_core::prelude::*;
47    /// use tea_map::MapValidBasic;
48    ///
49    /// let v = vec![Some(1), None, Some(2), None, Some(3)];
50    /// let result: Vec<_> = v.titer().ffill_mask(|x| x.is_none(), Some(Some(0))).collect();
51    /// assert_eq!(result, vec![Some(1), Some(1), Some(2), Some(2), Some(3)]);
52    /// ```
53    fn ffill_mask<F: Fn(&T) -> bool>(
54        self,
55        mask_func: F,
56        value: Option<T>,
57    ) -> impl TrustedLen<Item = T> {
58        let mut last_valid: Option<T> = None;
59        let f = move |v: T| {
60            if mask_func(&v) {
61                if let Some(lv) = last_valid.as_ref() {
62                    lv.clone()
63                } else if let Some(value) = &value {
64                    value.clone()
65                } else {
66                    T::none()
67                }
68            } else {
69                // v is valid, update last_valid
70                last_valid = Some(v.clone());
71                v
72            }
73        };
74        self.map(f)
75    }
76
77    /// Forward fill None values.
78    ///
79    /// This method is similar to `ffill()`, but it can handle None values.
80    ///
81    /// # Arguments
82    ///
83    /// * `value` - An optional value to fill if head values are still None after forward fill.
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use tea_core::prelude::*;
89    /// use tea_map::MapValidBasic;
90    ///
91    /// let v = vec![Some(1), None, Some(2), None, Some(3)];
92    /// let result: Vec<_> = v.titer().ffill(Some(Some(0))).collect();
93    /// assert_eq!(result, vec![Some(1), Some(1), Some(2), Some(2), Some(3)]);
94    /// ```
95    #[inline]
96    fn ffill(self, value: Option<T>) -> impl TrustedLen<Item = T> {
97        self.ffill_mask(T::is_none, value)
98    }
99
100    /// Backward fill values where the mask is true, ignoring None values.
101    ///
102    /// # Arguments
103    ///
104    /// * `mask_func` - A function that returns true for values that should be filled.
105    /// * `value` - An optional value to fill if tail values are still None after backward fill.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use tea_core::prelude::*;
111    /// use tea_map::MapValidBasic;
112    ///
113    /// let v = vec![Some(1), None, Some(2), None, Some(3)];
114    /// let result: Vec<_> = v.titer().bfill_mask(|x| x.is_none(), Some(Some(0))).collect();
115    /// assert_eq!(result, vec![Some(1), Some(2), Some(2), Some(3), Some(3)]);
116    /// ```
117    fn bfill_mask<F: Fn(&T) -> bool>(
118        self,
119        mask_func: F,
120        value: Option<T>,
121    ) -> impl TrustedLen<Item = T>
122    where
123        Self: DoubleEndedIterator<Item = T>,
124    {
125        let mut last_valid: Option<T> = None;
126        let f = move |v: T| {
127            if mask_func(&v) {
128                if let Some(lv) = last_valid.as_ref() {
129                    lv.clone()
130                } else if let Some(value) = &value {
131                    value.clone()
132                } else {
133                    T::none()
134                }
135            } else {
136                // v is valid, update last_valid
137                last_valid = Some(v.clone());
138                v
139            }
140        };
141        // if we use `self.rev().map(f).rev()` here, we will get a wrong result
142        // so collect the result to a vec and then return rev iterator
143        self.rev().map(f).collect_trusted_to_vec().into_iter().rev()
144    }
145
146    /// Backward fill None values.
147    ///
148    /// This method is similar to `bfill()`, but it can handle None values.
149    ///
150    /// # Arguments
151    ///
152    /// * `value` - An optional value to fill if tail values are still None after backward fill.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use tea_core::prelude::*;
158    /// use tea_map::MapValidBasic;
159    ///
160    /// let v = vec![Some(1), None, Some(2), None, Some(3)];
161    /// let result: Vec<_> = v.titer().bfill(Some(Some(0))).collect();
162    /// assert_eq!(result, vec![Some(1), Some(2), Some(2), Some(3), Some(3)]);
163    /// ```
164    #[inline]
165    fn bfill(self, value: Option<T>) -> impl TrustedLen<Item = T>
166    where
167        Self: DoubleEndedIterator<Item = T>,
168    {
169        self.bfill_mask(T::is_none, value)
170    }
171
172    /// Clip (limit) the values in an iterator, ignoring None values.
173    ///
174    /// This method is similar to `clip()`, but it can handle None values.
175    ///
176    /// # Arguments
177    ///
178    /// * `lower` - The lower bound.
179    /// * `upper` - The upper bound.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use tea_core::prelude::*;
185    /// use tea_map::MapValidBasic;
186    ///
187    /// let v = vec![Some(1), None, Some(3), Some(5), Some(7)];
188    /// let result: Vec<_> = v.titer().vclip(Some(2), Some(6)).collect();
189    /// assert_eq!(result, vec![Some(2), None, Some(3), Some(5), Some(6)]);
190    /// ```
191    #[inline]
192    fn vclip<'a>(self, lower: T, upper: T) -> Box<dyn TrustedLen<Item = T> + 'a>
193    where
194        T::Inner: PartialOrd,
195        T: 'a,
196        Self: 'a,
197    {
198        let lower_flag = lower.not_none();
199        let upper_flag = upper.not_none();
200        match (lower_flag, upper_flag) {
201            (true, true) => {
202                let (lower_inner, upper_inner) = (lower.clone().unwrap(), upper.clone().unwrap());
203                Box::new(self.map(move |v| {
204                    if v.not_none() {
205                        let v_inner = v.clone().unwrap();
206                        if v_inner < lower_inner {
207                            lower.clone()
208                        } else if v_inner > upper_inner {
209                            upper.clone()
210                        } else {
211                            v
212                        }
213                    } else {
214                        v
215                    }
216                }))
217            },
218            (true, false) => {
219                let lower_inner = lower.clone().unwrap();
220                Box::new(self.map(move |v: T| {
221                    if v.not_none() && (v.clone().unwrap() < lower_inner) {
222                        lower.clone()
223                    } else {
224                        v
225                    }
226                }))
227            },
228            (false, true) => {
229                let upper_inner = upper.clone().unwrap();
230                Box::new(self.map(move |v: T| {
231                    if v.not_none() && (v.clone().unwrap() > upper_inner) {
232                        upper.clone()
233                    } else {
234                        v
235                    }
236                }))
237            },
238            (false, false) => Box::new(self),
239        }
240    }
241
242    /// Fill values where the mask is true, ignoring None values.
243    ///
244    /// # Arguments
245    ///
246    /// * `mask_func` - A function that returns true for values that should be filled.
247    /// * `value` - The value to fill with.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use tea_core::prelude::*;
253    /// use tea_map::MapValidBasic;
254    ///
255    /// let v = vec![Some(1), None, Some(3), Some(4), Some(5)];
256    /// let result: Vec<_> = v.titer().fill_mask(|x| x.map_or(false, |v| v % 2 == 0), Some(0)).collect();
257    /// assert_eq!(result, vec![Some(1), None, Some(3), Some(0), Some(5)]);
258    /// ```
259    #[inline]
260    fn fill_mask<F: Fn(&T) -> bool>(self, mask_func: F, value: T) -> impl TrustedLen<Item = T> {
261        self.map(move |v| if mask_func(&v) { value.clone() } else { v })
262    }
263
264    /// Fill None values with a specified value.
265    ///
266    /// This method is similar to `fill()`, but it can handle None values.
267    ///
268    /// # Arguments
269    ///
270    /// * `value` - The value to fill None values with.
271    ///
272    /// # Examples
273    ///
274    /// ```
275    /// use tea_core::prelude::*;
276    /// use tea_map::MapValidBasic;
277    ///
278    /// let v = vec![Some(1), None, Some(3), None, Some(5)];
279    /// let result: Vec<_> = v.titer().fill(Some(0)).collect();
280    /// assert_eq!(result, vec![Some(1), Some(0), Some(3), Some(0), Some(5)]);
281    /// ```
282    #[inline]
283    fn fill(self, value: T) -> impl TrustedLen<Item = T> {
284        self.fill_mask(T::is_none, value)
285    }
286
287    /// Shift the elements in the iterator, ignoring None values.
288    ///
289    /// This method is similar to [`shift()`](crate::MapBasic::shift), but it can handle None values.
290    ///
291    /// # Arguments
292    ///
293    /// * `n` - The number of positions to shift. Positive values shift right, negative values shift left.
294    /// * `value` - An optional value to fill the vacated positions.
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use tea_core::prelude::*;
300    /// use tea_map::MapValidBasic;
301    ///
302    /// let v = vec![Some(1), None, Some(3), Some(4), Some(5)];
303    /// let result: Vec<_> = v.titer().vshift(2, Some(Some(0))).collect();
304    /// assert_eq!(result, vec![Some(0), Some(0), Some(1), None, Some(3)]);
305    /// ```
306    ///
307    /// See also: [`shift()`](crate::MapBasic::shift)
308    fn vshift<'a>(self, n: i32, value: Option<T>) -> Box<dyn TrustedLen<Item = T> + 'a>
309    where
310        T: Clone + 'a,
311        Self: 'a,
312    {
313        let len = self.len();
314        let n_abs = n.unsigned_abs() as usize;
315        let value = value.unwrap_or_else(|| T::none());
316        if len <= n_abs {
317            return Box::new(std::iter::repeat(value).take(len));
318        }
319        match n {
320            n if n > 0 => Box::new(
321                std::iter::repeat(value)
322                    .take(n_abs)
323                    .chain(self.take(len - n_abs))
324                    .to_trust(len),
325            ),
326            n if n < 0 => Box::new(
327                self.skip(n_abs)
328                    .chain(std::iter::repeat(value).take(n_abs))
329                    .to_trust(len),
330            ),
331            _ => Box::new(self),
332        }
333    }
334
335    /// Drop None values from the iterator.
336    ///
337    /// # Examples
338    ///
339    /// ```
340    /// use tea_core::prelude::*;
341    /// use tea_map::MapValidBasic;
342    ///
343    /// let v = vec![Some(1), None, Some(3), None, Some(5)];
344    /// let result: Vec<_> = v.titer().drop_none().collect();
345    /// assert_eq!(result, vec![Some(1), Some(3), Some(5)]);
346    /// ```
347    #[inline]
348    fn drop_none(self) -> impl Iterator<Item = T> {
349        self.filter(T::not_none)
350    }
351    /// Categorize values into bins.
352    ///
353    /// This function categorizes the values in the iterator into bins defined by the `bins` parameter.
354    /// It assigns labels to each bin as specified by the `labels` parameter.
355    ///
356    /// # Arguments
357    ///
358    /// * `bins` - A slice of bin edges.
359    /// * `labels` - A slice of labels for each bin.
360    /// * `right` - If true, intervals are closed on the right. If false, intervals are closed on the left.
361    /// * `add_bounds` - If true, adds -∞ and +∞ as the first and last bin edges respectively.
362    ///
363    /// # Returns
364    ///
365    /// Returns a `TResult` containing a boxed `TrustedLen` iterator of `TResult<T2>` items.
366    ///
367    /// # Errors
368    ///
369    /// Returns an error if:
370    /// - The number of labels doesn't match the number of bins (accounting for `add_bounds`).
371    /// - A value falls outside the bin ranges.
372    ///
373    /// # Examples
374    ///
375    /// ```
376    /// use tea_core::prelude::*;
377    /// use tea_map::MapValidBasic;
378    ///
379    /// let v = vec![1, 3, 5, 7, 9];
380    /// let bins = vec![4, 8];
381    /// let labels = vec!["low", "medium", "high"];
382    /// let result: Vec<_> = v.titer().vcut(&bins, &labels, true, true).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
383    /// assert_eq!(result, vec!["low", "low", "medium", "medium", "high"]);
384    /// ```
385    fn vcut<'a, V2, V3, T2>(
386        self,
387        bins: &'a V2,
388        labels: &'a V3,
389        right: bool,
390        add_bounds: bool,
391    ) -> TResult<Box<dyn TrustedLen<Item = TResult<T2>> + 'a>>
392    where
393        Self: 'a,
394        T::Inner: Number + Debug,
395        (T::Inner, T::Inner): itertools::traits::HomogeneousTuple<Item = T::Inner>,
396        T2: IsNone + 'a,
397        V2: Vec1View<T>,
398        V3: Vec1View<T2>,
399    {
400        use itertools::Itertools;
401        let bins: Vec<T::Inner> = if add_bounds {
402            if labels.len() != bins.len() + 1 {
403                tbail!(func=cut, "Number of labels must be one more than the number of bin edges, label: {}, bins: {}", labels.len(), bins.len())
404            }
405            vec![T::Inner::min_()]
406                .into_iter()
407                .chain(bins.titer().map(IsNone::unwrap))
408                .chain(vec![T::Inner::max_()])
409                .collect()
410        } else {
411            if labels.len() + 1 != bins.len() {
412                tbail!(func=cut, "Number of labels must be one fewer than the number of bin edges, label: {}, bins: {}", labels.len(), bins.len())
413            }
414            bins.titer().map(IsNone::unwrap).collect_trusted_vec1()
415        };
416        if right {
417            Ok(Box::new(self.map(move |value| {
418                if value.is_none() {
419                    Ok(T2::none())
420                } else {
421                    let value = value.unwrap();
422                    let mut out = None;
423                    for (bound, label) in bins
424                        .titer()
425                        .tuple_windows::<(T::Inner, T::Inner)>()
426                        .zip(labels.titer())
427                    {
428                        if (bound.0 < value) && (value <= bound.1) {
429                            out = Some(label.clone());
430                            break;
431                        }
432                    }
433                    out.ok_or_else(|| terr!(func = cut, "value: {:?} not in bins", value))
434                }
435            })))
436        } else {
437            Ok(Box::new(self.map(move |value| {
438                if value.is_none() {
439                    Ok(T2::none())
440                } else {
441                    let value = value.unwrap();
442                    let mut out = None;
443                    for (bound, label) in bins
444                        .titer()
445                        .tuple_windows::<(T::Inner, T::Inner)>()
446                        .zip(labels.titer())
447                    {
448                        if (bound.0 <= value) && (value < bound.1) {
449                            out = Some(label.clone());
450                            break;
451                        }
452                    }
453                    out.ok_or_else(|| terr!(func = cut, "value: {:?} not in bins", value))
454                }
455            })))
456        }
457    }
458
459    /// Returns indices of unique elements in a sorted iterator, keeping either the first or last occurrence.
460    ///
461    /// # Arguments
462    ///
463    /// * `keep` - Specifies whether to keep the first or last occurrence of each unique element.
464    ///
465    /// # Returns
466    ///
467    /// A boxed iterator yielding indices of unique elements.
468    ///
469    /// # Examples
470    ///
471    /// ```
472    /// use tea_core::prelude::*;
473    /// use tea_map::{MapValidBasic, Keep};
474    ///
475    /// let v = vec![Some(1), Some(1), Some(2), Some(2), Some(3)];
476    /// let result: Vec<_> = v.titer().vsorted_unique_idx(Keep::First).collect();
477    /// assert_eq!(result, vec![0, 2, 4]);
478    ///
479    /// let result: Vec<_> = v.titer().vsorted_unique_idx(Keep::Last).collect();
480    /// assert_eq!(result, vec![1, 3, 4]);
481    /// ```
482    fn vsorted_unique_idx<'a>(self, keep: Keep) -> Box<dyn Iterator<Item = usize> + 'a>
483    where
484        T::Inner: PartialEq + 'a + std::fmt::Debug,
485        Self: 'a,
486    {
487        match keep {
488            Keep::First => {
489                let mut last_value = None;
490                let out = self.into_iter().enumerate().filter_map(move |(i, v)| {
491                    if v.not_none() {
492                        let v = v.unwrap();
493                        if last_value == Some(v.clone()) {
494                            None
495                        } else {
496                            last_value = Some(v);
497                            Some(i)
498                        }
499                    } else {
500                        None
501                    }
502                });
503                Box::new(out)
504            },
505            Keep::Last => {
506                let mut iter = self.into_iter();
507                let first_element = iter.next();
508                let mut last_value = if let Some(v) = first_element {
509                    if v.not_none() {
510                        Some(v.unwrap())
511                    } else {
512                        None
513                    }
514                } else {
515                    None
516                };
517                let out = iter
518                    .map(|v| v.to_opt())
519                    .chain(std::iter::once(None))
520                    .enumerate()
521                    .filter_map(move |(i, v)| {
522                        if v.not_none() {
523                            let v = v.unwrap();
524                            if last_value == Some(v.clone()) {
525                                None
526                            } else {
527                                last_value = Some(v);
528                                Some(i)
529                            }
530                        } else {
531                            let out = if last_value.is_some() { Some(i) } else { None };
532                            last_value = None;
533                            out
534                        }
535                    });
536                Box::new(out)
537            },
538        }
539    }
540
541    /// Returns an iterator over unique elements in a sorted iterator.
542    ///
543    /// This method removes consecutive duplicate elements from the iterator.
544    ///
545    /// # Returns
546    ///
547    /// An iterator yielding unique elements.
548    ///
549    /// # Examples
550    ///
551    /// ```
552    /// use tea_core::prelude::*;
553    /// use tea_map::MapValidBasic;
554    ///
555    /// let v = vec![Some(1), Some(1), Some(2), Some(2), Some(3)];
556    /// let result: Vec<_> = v.titer().vsorted_unique().collect();
557    /// assert_eq!(result, vec![Some(1), Some(2), Some(3)]);
558    /// ```
559    #[allow(clippy::unnecessary_filter_map)]
560    fn vsorted_unique<'a>(self) -> impl Iterator<Item = T> + 'a
561    where
562        T::Inner: PartialEq + 'a,
563        Self: 'a,
564    {
565        let mut value: Option<T::Inner> = None;
566        self.into_iter().filter_map(move |v| {
567            if v.not_none() {
568                let v = v.unwrap();
569                if let Some(last_v) = value.as_ref() {
570                    if v != last_v.clone() {
571                        value = Some(v.clone());
572                        Some(T::from_inner(v))
573                    } else {
574                        None
575                    }
576                } else {
577                    value = Some(v.clone());
578                    Some(T::from_inner(v))
579                }
580            } else {
581                None
582            }
583        })
584    }
585}
586
587impl<T: IsNone, I: TrustedLen<Item = T>> MapValidBasic<T> for I {}
588
589#[cfg(test)]
590mod test {
591    use tea_core::testing::assert_vec1d_equal_numeric;
592
593    use super::*;
594
595    #[test]
596    fn test_clip() {
597        let v = vec![1, 2, 3, 4, 5];
598        let res: Vec<_> = v.titer().vclip(2, 4).collect_trusted_vec1();
599        assert_eq!(res, vec![2, 2, 3, 4, 4]);
600        let v = vec![1., 2., 3., 4., 5.];
601        let res: Vec<_> = v.titer().vclip(2., f64::NAN).collect_trusted_vec1();
602        assert_eq!(&res, &vec![2., 2., 3., 4., 5.]);
603        let res: Vec<_> = v.titer().vclip(f64::NAN, 4.).collect_trusted_vec1();
604        assert_eq!(&res, &vec![1., 2., 3., 4., 4.]);
605        let res: Vec<_> = v.titer().vclip(f64::NAN, f64::NAN).collect_trusted_vec1();
606        assert_eq!(&res, &vec![1., 2., 3., 4., 5.]);
607    }
608
609    #[test]
610    fn test_fill() {
611        let v = vec![f64::NAN, 1., 2., f64::NAN, 3., f64::NAN];
612        let res: Vec<_> = v.titer().ffill(None).collect();
613        assert_vec1d_equal_numeric(&res, &vec![f64::NAN, 1., 2., 2., 3., 3.], None);
614        let res: Vec<_> = v.titer().ffill(Some(0.)).collect();
615        assert_vec1d_equal_numeric(&res, &vec![0., 1., 2., 2., 3., 3.], None);
616        let res: Vec<_> = v.titer().bfill(None).collect();
617        assert_vec1d_equal_numeric(&res, &vec![1., 1., 2., 3., 3., f64::NAN], None);
618        let res: Vec<_> = v.titer().bfill(Some(0.)).collect();
619        assert_vec1d_equal_numeric(&res, &vec![1., 1., 2., 3., 3., 0.], None);
620        let res: Vec<_> = v.titer().fill(0.).collect();
621        assert_vec1d_equal_numeric(&res, &vec![0., 1., 2., 0., 3., 0.], None);
622    }
623
624    #[test]
625    fn test_vcut() -> TResult<()> {
626        let v = vec![1, 3, 5, 1, 5, 6, 7, 32, 1];
627        let bins = vec![2, 5, 8];
628        let labels = vec![1, 2, 3, 4];
629        let res1: Vec<_> = v
630            .titer()
631            .vcut(&bins, &labels, true, true)?
632            .try_collect_vec1()?;
633        assert_eq!(res1, vec![1, 2, 2, 1, 2, 3, 3, 4, 1]);
634        let res2: Vec<_> = v
635            .titer()
636            .vcut(&bins, &labels, false, true)?
637            .try_collect_trusted_vec1()?;
638        // bin label mismatch
639        assert_eq!(res2, vec![1, 2, 3, 1, 3, 3, 3, 4, 1]);
640        assert!(v.titer().vcut(&[3], &labels, true, true).is_err());
641        // value not in bins
642        let res: TResult<Vec<_>> = v
643            .titer()
644            .vcut(&[1, 2, 5, 8, 20], &labels, true, false)?
645            .try_collect_vec1();
646        assert!(res.is_err());
647        Ok(())
648    }
649
650    #[test]
651    fn test_sorted_unique() {
652        let v = vec![1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 5, 6];
653        let res: Vec<_> = v.titer().vsorted_unique_idx(Keep::First).collect();
654        assert_eq!(res, vec![0, 2, 5, 6, 10, 12]);
655        let res: Vec<_> = v.titer().vsorted_unique().collect();
656        assert_eq!(res, vec![1, 2, 3, 4, 5, 6]);
657        let res: Vec<_> = v.titer().vsorted_unique_idx(Keep::Last).collect();
658        assert_eq!(res, vec![1, 4, 5, 9, 11, 12]);
659        let v = vec![6, 6, 5, 5, 5, 4, 3, 3, 3, 3, 2, 2, 1];
660        let v2: Vec<_> = v.to_opt_iter().chain(None).collect();
661        let res: Vec<_> = v2.titer().vsorted_unique_idx(Keep::First).collect();
662        assert_eq!(res, vec![0, 2, 5, 6, 10, 12]);
663        let res: Vec<_> = v2.titer().vsorted_unique_idx(Keep::Last).collect();
664        assert_eq!(res, vec![1, 4, 5, 9, 11, 12]);
665        let res: Vec<_> = v2.titer().vsorted_unique().collect();
666        assert_eq!(
667            res,
668            vec![Some(6), Some(5), Some(4), Some(3), Some(2), Some(1)]
669        );
670        let v3: Vec<_> = v
671            .iter_cast::<f64>()
672            .chain(std::iter::once(f64::NAN))
673            .collect();
674        let res: Vec<_> = v3.titer().vsorted_unique_idx(Keep::First).collect();
675        assert_eq!(res, vec![0, 2, 5, 6, 10, 12]);
676        let res: Vec<_> = v3.titer().vsorted_unique_idx(Keep::Last).collect();
677        assert_eq!(res, vec![1, 4, 5, 9, 11, 12]);
678        let res: Vec<_> = v3.titer().vsorted_unique().collect();
679        assert_eq!(res, vec![6., 5., 4., 3., 2., 1.]);
680        let v4 = vec![f64::NAN, f64::NAN, 4., 4., 2., 0., 0.];
681        let res: Vec<_> = v4.titer().vsorted_unique().collect();
682        assert_eq!(res, vec![4., 2., 0.]);
683        let res: Vec<_> = v4.titer().vsorted_unique_idx(Keep::First).collect();
684        assert_eq!(res, vec![2, 4, 5]);
685    }
686}