是否有更简洁或声明性的方式来初始化 HashMap?

Is there a more concise or declarative way to initialize a HashMap?

我正在使用 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' }

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

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' => 0, 'C' => 0, 'G' => 0, 'T' => 0]; 一样使用。

我在 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),
]);

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

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

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

您可以使用 选项 class 中鲜为人知的方法在 Rust 中实现相同的目的。说真的,当你有空的时候,通读一下 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(),returns 值,如果是 None, return 是您提供的值。 请注意,即使该方法名为 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 return a None (想象一下,如果你想检查一个散列映射中的一个键,如果没有找到,检查 另一个 散列映射,并且,只有当两者都没有找到,您是否制作了 None)。因为 or else() return 是一个选项,我们必须使用 unwrap() (如果在 None,但我们知道这不适用于此处)。

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

use std::collections::HashMap;

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