我正在尝试解决leetcode问题MDN documentation。很容易,只需找出糖果种类和糖果一半数量之间的最小值即可。
这是我的解决方案(耗时48ms):
use std::collections::HashSet;
pub fn distribute_candies(candies: Vec<i32>) -> i32 {
let sister_candies = (candies.len() / 2) as i32;
let mut kind = 0;
let mut candies_kinds = HashSet::new();
for candy in candies.into_iter() {
if candies_kinds.insert(candy) {
kind += 1;
if kind > sister_candies {
return sister_candies;
}
}
}
kind
}
但是,我找到了使用迭代器的解决方案:
use std::collections::HashSet;
use std::cmp::min;
pub fn distribute_candies(candies: Vec<i32>) -> i32 {
min(candies.iter().collect::<HashSet<_>>().len(), candies.len() / 2) as i32
}
花费36毫秒。
我不太明白为什么迭代器解决方案比我的for
循环解决方案要快。 Rust在后台执行一些魔术优化吗?
答案 0 :(得分:4)
主要区别在于迭代器版本internally uses Iterator::size_hint
用于确定在收集之前在HashSet
中保留多少空间。这样可以避免随着集合的增长而重复地进行重新分配。
您可以使用HashSet::with_capacity
代替HashSet::new
来完成相同的操作:
let mut candies_kinds = HashSet::with_capacity(candies.len());
在我的基准测试中,此更改使您的代码比迭代器快得多。但是,如果我简化您的代码以消除早期的紧急救援优化,则其运行时间几乎与迭代器版本相同。
pub fn distribute_candies(candies: &[i32]) -> i32 {
let sister_candies = (candies.len() / 2) as i32;
let mut candies_kinds = HashSet::with_capacity(candies.len());
for candy in candies.into_iter() {
candies_kinds.insert(candy);
}
sister_candies.min(candies_kinds.len() as i32)
}
时间:
test tests::bench_iter ... bench: 262,315 ns/iter (+/- 23,704)
test tests::bench_loop ... bench: 307,697 ns/iter (+/- 16,119)
test tests::bench_loop_with_capacity ... bench: 112,194 ns/iter (+/- 18,295)
test tests::bench_loop_with_capacity_no_bailout ... bench: 259,961 ns/iter (+/- 17,712)
这向我表明HashSet
的预分配是主要差异。您的额外优化也被证明是非常有效的-至少对于我碰巧选择的数据集而言。