【问题标题】:How do I concisely fill a 2D HashMap?如何简洁地填充 2D HashMap?
【发布时间】:2017-08-16 10:58:04
【问题描述】:

有没有一种简单的方法来填充二维HashMap?在 C++ 中,我可以按照以下方式做一些事情:

std::unordered_map<int,std::unordered_map<int,int>> 2dmap;
//...
2dmap[0][0] = 0;
2dmap[0][1] = 1;
//...

在我的 Rust 项目中,我正在尝试填充类似的地图:

let mut map: HashMap<i32, HashMap<i32, i32>> = HashMap::new();
//...fill the hash map here.

我能想到的唯一方法是构建每个子地图,然后将它们移动到超级地图中,如下所示:

let mut sub1: HashMap<i32, i32> = HashMap::new();
sub1.insert(0, 0);
sub1.insert(1, 1);
let mut map: HashMap<i32, HashMap<i32, i32>>::new();
map.insert(0, sub1);

有没有更简洁的方法来做到这一点?

上面的代码是我实际用例的简化,它使用枚举作为HashMap的索引:

enum Example {
    Variant1,
    Variant2,
    //...
}

所有变体都没有值。我正在使用此语法从我的HashMap 进行查找:

let value = map[Example::Variant1][Example::Variant2];

【问题讨论】:

  • 既然元组在 Rust 中工作得很好,为什么不使用HashMap&lt;(i32,i32),i32&gt;
  • 修改后,您仍然可以使用HashMap&lt;(example, example),i32&gt;。你真的需要只用第一个example 进行查找吗?如果没有,您应该使用元组...
  • @Alec 是否认为使用元组会提供更紧凑的表示?或更快的查找?两个都?理由是什么?我不反对这个想法,我只是想了解其中的原因而不仅仅是解决方案。
  • 争论更多的是关于语义。顺便说一下,表示更紧凑和更快。额外的HashMaps 不会产生额外的开销,每个分配的内存比他们需要的多,并且只需要在表中查找一次而不是两次。

标签: hashmap rust


【解决方案1】:

C++ 代码 sn-p 的简洁性归功于方便的operator [],如果映射中缺少该值,它会自动默认构造该值。虽然 Rust 默认不这样做,但很容易告诉它这样做,如 Shepmaster 的回答所示。

为了避免拼写出entry(key).or_insert_with(ValueType::new),可以添加相当于C++的operator []的方法到Rust的HashMap。毕竟,Rust 有必要的工具——它支持使用 trait 向现有类型添加方法,并且它的 trait 大致相当于 C++ 的默认构造函数。

C++ 表达式:

map[0][0] = 0;
map[0][1] = 1;

将被写成以下 Rust,使用返回引用代替 operator [] 的方法:

*map.ensure(0).ensure(0) = 0;
*map.ensure(0).ensure(1) = 1;

ensure 将在一个特征中声明,该特征必须由想要获取该方法的代码导入:

use std::collections::HashMap;
use std::hash::Hash;

trait MapDefault<K, V: Default> {
    fn ensure(&mut self, key: K) -> &mut V;
}

...并定义如下:

impl<K: Eq + Hash, V: Default> MapDefault<K, V> for HashMap<K, V> {
    fn ensure(&mut self, key: K) -> &mut V {
        self.entry(key).or_insert_with(V::default)
    }
}

如果能够为HashMap 定义IndexMut 那就太好了,这会将表达式缩短为*map[0][0] = 0,这几乎与C++ 原始版本一模一样,但不幸的是,Rust 不允许实现类型的运算符来自其他模块。

【讨论】:

  • 你可以实现一个实现IndexMut的包装器类型吗?
  • 实际上,也许不是。 There's a reason that HashMap doesn't implement IndexMut,所以你真的做不到。该方法是目前更好的选择。
  • @Shepmaster 感谢您的提示,我将尝试在包装器类型上实现类似于ensureIndexMut::get_mutHashMap 本身无法实现答案中提出的 IndexMut 类型,因为它的值不需要 Default 特征(恕我直言,它们也不应该 - 该功能不值得)。之前存在和删除的IndexMut 将只是在一个不存在的键上panic,这绝对不是程序员所期望的map[k] = v 的语义。
【解决方案2】:

在 Rust 中,元组工作得非常好。您可能应该只使用HashMap&lt;(i32, i32), i32&gt;。然后,你会得到非常接近 C++ 代码的东西。

let mut map: HashMap<(i32, i32), i32> = HashMap::new();
sub1.insert((0, 0), 0);
sub1.insert((0, 1), 1);
// ...

当然,如果我们有一个像 vec!an RFC is in the works 这样的宏,那就太好了。使用来自this answer 的宏,您可以编写:

let map = hashmap![(0,0) => 0, (0,1) => 1, ...];

如果您使用枚举,则不会发生任何变化 - 只需确保为它派生 EqPartialEqHash

let mut sub1: HashMap<(Example, Example), i32> = HashMap::new();
sub1.insert((Example::Variant1, Example::Variant1), 0);
sub1.insert((Example::Variant1, Example::Variant2), 1);
// ...

【讨论】:

  • 查找语法是什么?
  • @AlexZywicki map.get(&amp;(0,1))?
  • 没有理由指定HashMap的类型,类型推断会处理它。
  • @Shepmaster 哦,当然。但是,作为一个在我的大部分编程生涯中使用支持类型推断的语言(Haskell 和 Scala)工作的人,我仍然喜欢在类型不是立即明显的定义站点上添加注释。未来的我最终对现在的我更快乐。
  • @Alec “不安全”这个词在 Rust 中具有特定的含义。 map[&amp;(0, 1)] 是安全的(在 Rust 意义上),但它可能会恐慌。
【解决方案3】:

使用entry API:

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.entry(0).or_insert_with(HashMap::new).insert(0, 0);
    map.entry(0).or_insert_with(HashMap::new).insert(1, 1);

    println!("{:?}", map);
    println!("{}", map[&0][&1]);
}

与 C++ 不同,构造嵌套的 HashMap 不是隐式的 - 您必须非常清楚您希望允许创建新的 HashMap


the other answer 不同,这保留了原始数据结构和仅基于初始键获取整个映射子集的能力:

println!("{:?}", map.get(&0));
println!("{:?}", map.get(&0).and_then(|m| m.get(&1)));

如果您总是同时提供两个数字索引,那么元组是一个更好的解决方案,因为它可以更准确地模拟问题 - 只有一个真正的键,它恰好有两个组件。它还可能具有更好的数据局部性,因为有一大块内存,而不是需要额外级别的指针追逐哈希哈希。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-08
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 2021-07-07
    • 2013-12-19
    • 1970-01-01
    • 2015-05-12
    相关资源
    最近更新 更多