use std::collections::HashSet;
fn main () {
let strings = vec![
"apple", "cAt", "cat", "Dog", "apple", "dog", "Cat",
"Apple", "dOg", "banana", "cat", "dog", "apple",
];
let mut set : HashSet<&'static str> = HashSet::new();
let strings : Vec<&str> = strings
.into_iter()
.map(|string| (string, string.to_lowercase()))
.filter(|(_, lower)| set.insert(lower))
.map(|(string, _)| string)
.collect();
println!("{:?}", strings);
}Changing the type annotations won't make this compile, because it is actually catching a real bug. The iterator is lazy and the full pipeline is executed for each element at once: lowercasing, inserting into the set, inserting into the Vec that's the result of the collect, and deallocating the lower string. This last step is the key/danger: if the HashSet held references/slices to the lower strings (instead of owning them), those references would become dangling immediately and future look-ups into the set won't work right/will trigger undefined behaviour.
The problem is a little clearer (and mostly fixed) if you simplify the code slightly by removing the two map calls, and instead call to_lowercase in the filter directly:
.into_iter()
.filter(|string| set.insert(string.to_lowercase()))
.collect();
This form is a type error, that can be corrected by changing the type annotation to be HashSet<String>, or even removing it entirely and letting type inference handle it. The HashSet owning the strings is the key, so they only disappear after the entire iteration is complete, not after each element.The UniCase crate defines a wrapper around strings with a case-insensitive Eq implementation, so this works:
use std::collections::HashSet;
use unicase::UniCase;
fn main () {
let strings = vec![
"apple", "cAt", "cat", "Dog", "apple", "dog", "Cat",
"Apple", "dOg", "banana", "cat", "dog", "apple",
];
let mut set = HashSet::new();
let strings: Vec<_> = strings
.iter()
.filter(|&string| set.insert(UniCase::new(string)))
.collect();
println!("{:?}", strings);
}
https://play.rust-lang.org/?version=stable&mode=debug&editio... let dedup = strings.into_iter().map(|s| s.to_lowercase()).collect::<HashSet<_>>().into_iter().collect::<Vec<_>>(); set.insert(string.to_lowercase())
It also requires adjusting/deleting the type annotation on the set.https://play.rust-lang.org/?version=stable&mode=debug&editio...
* that the uniqueness check is done case-insensitively,
* that the casing from the first instance of the string in the input array should be used in the output, and
* that the output should be ordered based on the order of the first instance of the string in the input array
Possibly an unusual set of requirements (I'd generally expect that if removing duplicates from a list, that the output order wouldn't matter) but hey, different problems have different requirements.
The desired output is:
while that code snippet produces: