askama/filters/
std.rs

1use core::convert::Infallible;
2use std::collections::HashMap;
3use std::collections::hash_map::Entry;
4use std::hash::Hash;
5use std::rc::Rc;
6
7/// Returns an iterator with all duplicates removed.
8///
9/// The sorting order is kept, no extra allocation is performed. However, to make it possible,
10/// the data is wrapped inside `Rc`.
11///
12/// ```
13/// # use askama::Template;
14/// #[derive(Template)]
15/// #[template(ext = "html", source = "{% for elem in example|unique %}{{ elem }},{% endfor %}")]
16/// struct Example<'a> {
17///     example: Vec<&'a str>,
18/// }
19///
20/// assert_eq!(
21///     Example { example: vec!["a", "b", "a", "c"] }.to_string(),
22///     "a,b,c,"
23/// );
24/// ```
25pub fn unique<T: Hash + Eq>(
26    it: impl IntoIterator<Item = T>,
27) -> Result<impl Iterator<Item = Rc<T>>, Infallible> {
28    let mut set = HashMap::new();
29
30    Ok(it.into_iter().filter_map(move |elem| {
31        // To prevent cloning the data, we need to use `Rc`, like that we can clone `elem` as
32        // key of the `HashSet` and return it.
33        if let Entry::Vacant(entry) = set.entry(Rc::new(elem)) {
34            Some(Rc::clone(entry.insert_entry(()).key()))
35        } else {
36            None
37        }
38    }))
39}
40
41#[cfg(test)]
42mod test {
43    use alloc::vec; // It's the macro, not the module.
44    use alloc::vec::Vec;
45
46    use super::*;
47
48    #[test]
49    fn test_unique() {
50        assert_eq!(
51            unique(["a", "b", "a", "c"]).unwrap().collect::<Vec<_>>(),
52            vec![Rc::new("a"), Rc::new("b"), Rc::new("c")]
53        );
54        assert_eq!(
55            unique([1, 1, 1, 2, 1]).unwrap().collect::<Vec<_>>(),
56            vec![Rc::new(1), Rc::new(2)]
57        );
58        assert_eq!(
59            unique("hello".chars()).unwrap().collect::<Vec<_>>(),
60            vec![Rc::new('h'), Rc::new('e'), Rc::new('l'), Rc::new('o')]
61        );
62    }
63}