如何创建HashMap文字?


87

如何在Rust中创建HashMap文字?在Python中,我可以这样做:

hashmap = {
   'element0': {
       'name': 'My New Element',
       'childs': {
           'child0': {
               'name': 'Child For Element 0',
               'childs': {
                   ...
               }
           }
       }
   },
   ...
}

在Go中这样:

type Node struct {
    name string
    childs map[string]Node
}

hashmap := map[string]Node {
    "element0": Node{
        "My New Element",
        map[string]Node {
            'child0': Node{
                "Child For Element 0",
                map[string]Node {}
            }
        }
    }
}

Answers:


90

Rust中没有地图文字语法。我不知道确切的原因,但我希望有多个像map一样运作的数据结构(例如BTreeMapHashMap),这将使选择一个困难。

但是,您可以创建一个宏来为您完成工作,如为什么此rust HashMap宏不再起作用?。这是该宏进行了一些简化,并具有足以使其在操场上可运行的结构:

macro_rules! map(
    { $($key:expr => $value:expr),+ } => {
        {
            let mut m = ::std::collections::HashMap::new();
            $(
                m.insert($key, $value);
            )+
            m
        }
     };
);

fn main() {
    let names = map!{ 1 => "one", 2 => "two" };
    println!("{} -> {:?}", 1, names.get(&1));
    println!("{} -> {:?}", 10, names.get(&10));
}

此宏避免分配不必要的中间体Vec,但是它不使用,HashMap::with_capacity因此在HashMap添加值时可能会有一些无用的重新分配。可以使用更复杂的版本来计算值的宏,但是性能上的好处可能并不是大多数使用该宏的优点。


在锈nightly版本,就能避免双方不必要的分配,也需要有一个宏(和再分配!):

#![feature(array_value_iter)]

use std::array::IntoIter;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::iter::FromIterator;

fn main() {
    let s = Vec::from_iter(IntoIter::new([1, 2, 3]));
    println!("{:?}", s);

    let s = BTreeSet::from_iter(IntoIter::new([1, 2, 3]));
    println!("{:?}", s);

    let s = HashSet::<_>::from_iter(IntoIter::new([1, 2, 3]));
    println!("{:?}", s);

    let s = BTreeMap::from_iter(IntoIter::new([(1, 2), (3, 4)]));
    println!("{:?}", s);

    let s = HashMap::<_, _>::from_iter(IntoIter::new([(1, 2), (3, 4)]));
    println!("{:?}", s);
}

然后,也可以将此逻辑包装回宏:

#![feature(array_value_iter)]

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};

macro_rules! collection {
    // map-like
    ($($k:expr => $v:expr),* $(,)?) => {
        std::iter::Iterator::collect(std::array::IntoIter::new([$(($k, $v),)*]))
    };
    // set-like
    ($($v:expr),* $(,)?) => {
        std::iter::Iterator::collect(std::array::IntoIter::new([$($v,)*]))
    };
}

fn main() {
    let s: Vec<_> = collection![1, 2, 3];
    println!("{:?}", s);

    let s: BTreeSet<_> = collection! { 1, 2, 3 };
    println!("{:?}", s);

    let s: HashSet<_> = collection! { 1, 2, 3 };
    println!("{:?}", s);

    let s: BTreeMap<_, _> = collection! { 1 => 2, 3 => 4 };
    println!("{:?}", s);

    let s: HashMap<_, _> = collection! { 1 => 2, 3 => 4 };
    println!("{:?}", s);
}

也可以看看:


6
grabbag_macros箱子里也有一个。您可以在此处查看源代码:github.com/DanielKeep/rust-grabbag/blob/master/grabbag_macros/…
DK。

4
啊,真可惜 我喜欢Swift的方法,它从类型中提取文字。DictionaryLiteral可以使用A初始化任何符合的类型ExpressibleByDictionaryLiteral(即使标准库提供了这样一种类型Dictionary
Alexander Alexander

48

我建议使用maplit板条箱。

引用文档:

具有特定类型的容器文字的宏。

use maplit::hashmap;

let map = hashmap!{
    "a" => 1,
    "b" => 2,
};

maplit板条箱将=>语法用于映射宏。:由于语法上常规macro_rules!宏的限制,因此无法将其用作分隔符。

请注意,rust宏非常灵活,您可以在其中使用方括号进行调用。您可以将它们用作hashmap!{}hashmap![]hashmap!()。此板条箱建议{}作为映射&集宏的约定,它匹配其调试输出。

巨集

  • btreemapBTreeMap根据键值对列表 创建一个
  • btreesetBTreeSet从元素列表 创建一个。
  • hashmapHashMap根据键值对列表 创建一个
  • hashsetHashSet从元素列表 创建一个。

42

文档中提供HashMap了一个有关如何实现此目的的示例:

let timber_resources: HashMap<&str, i32> = [("Norway", 100), ("Denmark", 50), ("Iceland", 10)]
    .iter()
    .cloned()
    .collect();

4
虽然在这种情况下可以使用,但使用诸如String代替时,它会变得不必要地昂贵&str
Shepmaster

7
当然存在运行时成本,但也存在与添加其他板条箱依存关系或定义难以理解的宏相关的成本。大多数代码不是对性能至关重要的,我发现此版本可读性强。
jupp0r 18-10-15

2
此外,这不适用于非克隆类型。
哈奇·摩尔

10
这个例子可以调整,以避免这些问题,通过使用vec![ (name, value) ].into_iter().collect()
约翰内斯

1
@Stargateur,因为我不进行过早优化,您也不应该。可能是您的程序将从更优化的HashMap文字初始化中受益,但可能不会。如果要获得最佳性能,请测量并优化程序中关键路径上的各个部分。随机优化内容只是一个坏策略。我的回答对99.9999%的人来说很好。它可读性强,编译速度快,不会引入依赖关系,而依赖关系会增加复杂性并不会带来潜在的安全漏洞。
jupp0r

4

正如@Johannes在评论中指出的,可以使用它的vec![]原因是:

  • Vec<T>实现IntoIterator<T>特质
  • HashMap<K, V> 实施 FromIterator<Item = (K, V)>

这意味着您可以执行以下操作:

let map: HashMap<String, String> = vec![("key".to_string(), "value".to_string())]
    .into_iter()
    .collect();

您可以使用,&str但如果不是,则可能需要注释生命周期'static

let map: HashMap<&str, usize> = vec![("one", 1), ("two", 2)].into_iter().collect();

这会不会代表二进制中的向量文字,然后在运行时将其收集到哈希图中?
Alex elias

1

您可以使用velcro板条箱*。maplit正如其他答案中所建议的,它类似于,但是具有更多的集合类型,更好的语法(至少在我看来!)和更多的功能。

假设您要使用String而不是&str,则您的确切示例将如下所示:

use std::collections::HashMap;
use velcro::hash_map;

struct Node {
    name: String
    children: HashMap<String, Node>,
}

let map = hash_map! {
    String::from("element0"): Node {
        name: "My New Element".into(),
        children: hash_map! {
            String::from("child0"): Node {
                name: "child0".into(),
                children: hash_map!{}
            }
        }
    }
};

由于Strings的构造方式,这有点丑陋。但是可以使用hash_map_from!它自动进行转换,从而在不更改键类型的情况下使它变得更整洁:

use std::collections::HashMap;
use velcro::{hash_map, hash_map_from};

let map: HashMap<String, Node> = hash_map_from! {
    "element0": Node {
        name: "My New Element".into(),
        children: hash_map_from! {
            "child0": Node {
                name: "child0".into(),
                children: hash_map!{}
            }
        }
    }
};

这是不是比去版本更详细。


*完全公开:我是该板条箱的作者。


0

对于一个元素

如果您希望只用一行中的一个元素来初始化地图(并且代码中没有可见的突变),则可以执行以下操作:

let map: HashMap<&'static str, u32> = Some(("answer", 42)).into_iter().collect();

这要感谢的用处Option能够在成为Iterator使用into_iter()

在实际代码中,您可能不需要帮助编译器使用以下类型:

use std::collections::HashMap;

fn john_wick() -> HashMap<&'static str, u32> {
    Some(("answer", 42)).into_iter().collect()
}

fn main() {
    let result = john_wick();

    let mut expected = HashMap::new();
    expected.insert("answer", 42);

    assert_eq!(result, expected);
}

还有一种方法可以将其链接起来,以使多个元素执行类似的操作Some(a).into_iter().chain(Some(b).into_iter()).collect(),但是这种方法较长,可读性较低,并且可能存在一些优化问题,因此我建议不要这样做。


-2

我见过很多不错的解决方案,但我只想简单一些。为此,这是一个特质:

use std::collections::HashMap;

trait Hash {
   fn to_map(&self) -> HashMap<&str, u16>;
}

impl Hash for [(&str, u16)] {
   fn to_map(&self) -> HashMap<&str, u16> {
      self.iter().cloned().collect()
   }
}

fn main() {
   let m = [("year", 2019), ("month", 12)].to_map();
   println!("{:?}", m)
}

我认为这是一个不错的选择,因为它实际上是Ruby和Nim已经使用的东西:


2
“我认为它是一个不错的选择,因为它实际上是Ruby和Nim已经使用的东西”。为什么红宝石或尼姆这样做会是一个更好的论据,然后只是“他们这样做就很好了”
Stargateur

没有争论的观点在SO答案中没有用
Stargateur

2
详细信息:将特征称为“哈希”是个坏主意:已经有一个具有该名称的特征,在rust中使用哈希运算时,您会很快遇到它。
DenysSéguret20年

2
我也建议copied()不要使用不实现复制的类型,避免无所事事的克隆,或者如果您还希望仅使用克隆的类型,则至少使用cloned_to_map()将其明确显示。
星际之门
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.