【问题标题】:More concise HashMap initialization更简洁的 HashMap 初始化
【发布时间】:2015-04-08 03:27:29
【问题描述】:

我正在使用HashMap 来计算字符串中不同字符的出现次数:

let text = "GATTACA";
let mut counts: HashMap<char, i32> = HashMap::new();
counts.insert('A', 0);
counts.insert('C', 0);
counts.insert('G', 0);
counts.insert('T', 0);

for c in text.chars() {
    match counts.get_mut(&c) {
        Some(x) => *x += 1,
        None => (),
    }
}

是否有更简洁或声明性的方式来初始化HashMap?例如在 Python 中我会这样做:

counts = { 'A': 0, 'C': 0, 'G': 0, 'T': 0 }

counts = { key: 0 for key in 'ACGT' }

【问题讨论】:

  • 同意。我应该搜索“literal”。
  • 5 年后,我在做同样的 Exercism.io 练习,我发现了这个 :)
  • 我相信我最初是在做一个 rosalind.info 问题,但显然这个答案非常有用。

标签: rust hashmap


【解决方案1】:

您可以使用迭代器来模拟字典理解,例如

let counts = "ACGT".chars().map(|c| (c, 0_i32)).collect::<HashMap<_, _>>();

甚至for c in "ACGT".chars() { counts.insert(c, 0) }

另外,可以编写一个宏来允许对任意值进行简明的初始化。

macro_rules! hashmap {
    ($( $key: expr => $val: expr ),*) => {{
         let mut map = ::std::collections::HashMap::new();
         $( map.insert($key, $val); )*
         map
    }}
}

let counts = hashmap!['A' =&gt; 0, 'C' =&gt; 0, 'G' =&gt; 0, 'T' =&gt; 0];一样使用。

【讨论】:

  • 那绝对应该在标准中!
  • 太好了,给我(新手)一个宏的例子!谢谢!
  • @PiotrZolnierek 这实际上是 在 std 中! (见我的回答:))
【解决方案2】:

我在official documentation 看到的另一种方式:

use std::collections::HashMap;

fn main() {
    let timber_resources: HashMap<&str, i32> =
    [("Norway", 100),
     ("Denmark", 50),
     ("Iceland", 10)]
     .iter().cloned().collect();
    // use the values stored in map
}

编辑

当我再次访问官方文档时,我看到示例已更新(并且旧示例已被删除)。所以这里是 Rust 1.56 的最新解决方案:

let vikings = HashMap::from([
    ("Norway", 25),
    ("Denmark", 24),
    ("Iceland", 12),
]);

【讨论】:

  • 为什么不into_iter
  • 是的,我刚刚做了,它不起作用。我意识到这与into_iter 对数组的限制有关。它仍然为您提供参考,就像 iter 一样,因此没有必要更新答案。
  • @TommasoTheaCioni 好消息是,在 2021 版中,into_iter() 适用于数组 as expected
  • 见@Daniel-Giger 回答:你需要Rust 1.56 版。
  • @Ted 谢谢我已经更新了解释,这样 >=1.56 和
【解决方案3】:

从 Rust 1.56 开始,您可以使用 from() 从键值对数组中构建 Hashmap。这使得无需指定类型或编写宏即可简洁地初始化。

use std::collections::HashMap;

fn main() {
    let m = HashMap::from([
        ('A', 0),
        ('C', 0),
        ('G', 0),
        ('T', 0)
    ]);
}

【讨论】:

  • 感谢您指出版本号。我一直在使用 1.54,无法让 HashMap::from() 工作。
【解决方案4】:

这个(非常常见的)场景就是为什么当我发现 Python 的 defaultdict 时,我听到天使在唱歌,这是一个字典,如果你尝试获取字典中没有的键,立即 为该键创建一个默认值,使用您在声明 defaultdict 时提供的构造函数。因此,在 Python 中,您可以执行以下操作:

counts = defaultdict(lambda: 0)
counts['A'] = counts['A'] + 1

对于计数出现次数,这是一种受欢迎的方法,因为当键空间很大或程序员不知道时,尝试预填充哈希表会出现问题(想象一下在你输入给它的文本中计算单词的东西。你打算预填充所有英语单词?如果一个新单词进入词典怎么办?)。

您可以在 Rust 中使用 Option 类中鲜为人知的方法来实现同样的目的。说真的,当您有空闲时间时,只需阅读 Option 中的所有方法。里面有一些非常方便的方法。

虽然没有处理简洁的初始化(这是 wubject 所要求的),但这里有两个答案(可以说,它们更适合做 OP 试图做的事情)。

let text = "GATTACA";
let mut counts:HashMap<char,i32> = HashMap::new();
for c in text.chars() {
    counts.insert(c,*(counts.get(&c).get_or_insert(&0))+1);
}

上述方法使用 Option 的 get 或 insert() 方法,如果是 Some(),则返回值,如果是 None,则返回您提供的值。 请注意,即使该方法被命名为 get_or_insert(),它不是插入到 hashmap 中;这是 Option 的一种方法,hashmap 不知道这种故障转移正在发生。好的一点是,这会为您解开价值。这与 Python 的 defaultdict 非常相似,不同之处在于您必须在代码中的多个位置提供默认值(引起错误,但也提供 defaultdict 缺乏的额外灵活性)。

let text = "GATTACA";
let mut counts:HashMap<char,i32> = HashMap::new();
for c in text.chars() {
    counts.insert(c,counts.get(&c).or_else(|| Some(&0)).unwrap()+1);
}

此方法使用 Option 的 or else() 方法,该方法允许您指定一个 lambda 来生成值,并且至关重要的是,您可以 still 返回一个 None(想象一下,如果您想要检查一个键的哈希映射,如果没有找到,检查它的 another 哈希映射,并且只有在任何一个都没有找到时,你才产生一个无)。因为 or else() 返回一个选项,所以我们必须使用 unwrap() (如果在 None 上使用它会恐慌,但我们知道这不适用于这里)。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-13
  • 2013-07-20
  • 2015-01-15
相关资源
最近更新 更多