【问题标题】:How to write a counter function for Vec<String> in Rust? [duplicate]如何在 Rust 中为 Vec<String> 编写计数器函数? [复制]
【发布时间】:2022-01-23 03:56:24
【问题描述】:

函数名称: my_counter
输入: ['foo', 'bar', 'bar', 'bar', 'bar']]
输出: {'foo': 1, 'bar': 4}

注意:输出类型是HashMap,而不是HashMap。

这是我的实现,我认为这有点开销。 'bar' 已被转换为字符串四次,但可能不需要。

pub fn my_counter(vec: &Vec<String>) -> HashMap<String, usize> {
    let mut result: HashMap<String, usize> = HashMap::new();
    for key in vec.iter() {
        let val = result.entry(key.to_string()).or_insert(0);
        *val += 1;
    }
    result
}

有人愿意分享更好的解决方案吗?非常感谢~

【问题讨论】:

    标签: arrays string rust hashmap


    【解决方案1】:

    您可以做的一件事是避免通过消耗(或移动)原始向量的值来创建新的Strings。像这样:

    pub fn my_counter(vec: Vec<String>) -> HashMap<String, usize> {
        let mut result: HashMap<String, usize> = HashMap::new();
        for key in vec {
            let val = result.entry(key).or_insert(0);
            *val += 1;
        }
        result
    }
    

    注意更改后的函数签名:vec 现在是 Vec&lt;String&gt;,而不是 &amp;Vec&lt;String&gt;。如果出于某种原因,这是不可接受的,那么您真的无法避免为HashMap 的键创建新的String 值。假设 N 是向量项的数量,而 M 是唯一向量项的数量。如果你知道你会在向量中有很多重复的值(也就是说,MN小很多),理论上你或许可以只创建 M 个新的Strings。但是,Rust 似乎没有提供 stable 方法来直接实现这一点。如果你可以使用 Nightly,不妨试试raw_entry_mut()

    另一种方法可能是首先创建一个临时HashMap&lt;&amp;str, usize&gt;,然后将其转换为所需的“完全拥有”HashMap&lt;String, usize&gt;。然而,这很可能只会让事情变得更糟。这实际上取决于您拥有的密钥。短键和 M:N 的比率约为 0.5-1.0 是一回事,如果你有长键和比率为 0.0001,则完全不同。

    如果您对唯一键的数量有一个很好的了解,您绝对可以通过简单地使用HashMap::with_capacity(...); 创建HashMap 来加快速度。理论上,使用默认哈希器的替代方案也可能有所帮助,尽管我只尝试过 FnvHashMap,即使是简短的 String 键,我也无法获得任何显着的加速。

    【讨论】:

    • 因为我是Rust新手,所以有点担心使用Nightly版的rust。据我所知,至少在数据分析或数据科学领域,大多数时候 M:N 应该小于 0.5,否则计算元素没有太大意义。所以我喜欢第二个隐藏HashMap类型的想法。非常感谢~
    【解决方案2】:

    我可以立即想象 3 种其他变体

    • 查看首先使用contains_key查看条目是否已经存在,然后使用insert或更新get_mut
    • 盲目只是insert,然后如果第一次插入返回之前的值,则重新insert
    • 首先构建一个HashMap&lt;&amp;str, usize&gt;,然后转换它(完成插入两次的工作)。

    问题是,什么更快取决于输入数据。有多少个字符串,它们有多长,输入 Vec 有多长。

    我已经benchmarked 5 例:

    • d1: 你输入数据的简短示例
    • few10k:10000 个条目的数组中有 10 个长度为 4 的唯一字符串
    • many10k:10000 个长度≤8 的条目,几乎都是唯一的
    • long: 300 个长度为 300 的唯一字符串
    • mix: 以上都是

    这是结果

    测试 d1_blind ... bench: 331 ns/iter (+/- 5) 测试 d1_seeing ... bench: 234 ns/iter (+/- 2) 测试 d1_tushushu ... bench: 230 ns/iter (+/- 4) 测试 d1_twice ... 工作台:232 ns/iter (+/- 4) 测试 few10k_blind ... bench: 727,628 ns/iter (+/- 4,557) 测试 few10k_seeing ... bench: 302,445 ns/iter (+/- 3,282) 测试 few10k_tushushu ... bench: 399,378 ns/iter (+/- 3,870) 测试few10k_twice ... bench: 173,828 ns/iter (+/- 2,431) 测试 long_blind ... bench: 70,604 ns/iter (+/- 233) 测试 long_seeing ... bench: 93,349 ns/iter (+/- 381) 测试 long_tushushu ... bench: 69,862 ns/iter (+/- 231) 测试 long_twice ... bench: 92,118 ns/iter (+/- 292) 测试 many10k_blind ... bench: 1,060,656 ns/iter (+/- 65,839) 测试 many10k_seeing ... bench: 1,148,671 ns/iter (+/- 58,452) 测试 many10k_tushushu ... bench: 957,733 ns/iter (+/- 5,392) 测试 many10k_twice ... bench: 1,297,526 ns/iter (+/- 57,415) 测试 mix_blind ... bench: 1,962,634 ns/iter (+/- 7,973) 测试 mix_seeing ... bench: 1,617,921 ns/iter (+/- 45,453) 测试 mix_tushushu ... bench: 1,519,876 ns/iter (+/- 4,872) 测试 mix_twice ... 工作台:1,597,130 ns/iter (+/- 11,068)

    在大多数情况下,您的实现似乎表现最好。您可以更进一步并开始混合实现,即运行twice 直到其HashMap&lt;&amp;str, usize&gt; 达到一定大小,然后切换到您的实现。由于这个问题出现在很多地方(例如,也在 sql 数据库中,并且那些人喜欢将事物优化到死),因此可能有人在某处写了一篇关于最佳方法的论文。

    【讨论】:

    • 我是 Rust 新手,实际上我对基准测试不太熟悉。因此,为我列出分数非常有帮助。我认为混合实现的想法很好,我也看到人们使用这种方式来解决其他问题。 (例如排序数组)。非常感谢~
    • 我可能应该提到运行基准测试:(0) 每晚生锈 (1) cargo new foobar (2) 将代码从操场放入 src/main.rs (3) 运行 cargo bench
    • 谢谢,让我试试cargo bench
    猜你喜欢
    • 1970-01-01
    • 2018-01-28
    • 2019-06-20
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-23
    相关资源
    最近更新 更多