为什么在链接迭代器而不是收集到临时HashSet时会得到不一致的结果?

时间:2018-09-18 16:19:00

标签: data-structures rust iterator hashset

我正在编写一个Rust函数,该函数接受一个数字列表和一个最大值,并将给定数字的所有倍数加总到最大(重复项仅计算一次)。我编写的函数的第一个版本是

use std::collections::HashSet;

pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 {
    let set: HashSet<u32> = factors
        .iter()
        .map(|factor| {
            let top: u32 = (limit - 1) / factor;

            (1..=top).map(move |num| num * factor)
        }).flatten()
        .collect();

    set.iter().fold(0, |acc, num| acc + num)
}

(我知道像这样合并HashSets可能不是最佳解决方案)。这样可以得到预期的结果:

println!("{}", sum_of_multiples(100, &[3, 5])) // 2318

当我在中间拨出对collect的呼叫并链接最后一个fold时,我得到一个不同的答案:

pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 {
    let val: u32 = factors
        .iter()
        .map(|factor| {
            let top: u32 = (limit - 1) / factor;

            (1..=top).map(move |num| num * factor)
        }).flatten()
        .fold(0, |acc, num| acc + num);

    val
}

结果:

println!("{}", sum_of_multiples(100, &[3, 5])) // 2633

我知道迭代器是延迟计算的,但是我假设它们是按使用顺序依次评估的。是因为flattenHashSet的行为?我不明白为什么第二轮结果不同,或者2633的意义(如果有)是什么。

2 个答案:

答案 0 :(得分:5)

您没有在第二个片段中删除重复项,因为您直接消耗了迭代器。

  

(我知道像这样合并HashSet可能不是最佳解决方案)。

由于需要临时存储来删除重复项,因此可以使用Vec然后将其放入集合中,或者可以使用Vec排序并过滤重复值来代替集合自己,但这需要测试。

  

此外,itertools提供了一个unique适配器,该适配器在内部跟踪唯一值。 – E_net4

这个也应该测试。这使您不必担心其实现方式。

最后,您可以在一个表达式中编写函数:

use std::collections::HashSet;

pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 {
    factors
        .iter()
        .flat_map(|factor| {
            let top = (limit - 1) / factor;

            (1..=top).map(move |num| num * factor)
        })
        .collect::<HashSet<u32>>()
        .iter()
        .sum()
}

答案 1 :(得分:4)

根据原始的要求说明,您已经删除了实际上用于某个目的的中间HashSet

  

重复项仅计算一次

省去了将值存储在HashSet中的步骤,这意味着将在每次出现重复项时对其进行计数,这应该可以解释差异。