rust 如何创建一个HashMap文本?

dy1byipe  于 2023-03-02  发布在  其他
关注(0)|答案(9)|浏览(150)

如何在Rust中创建一个HashMap常量?在Python中我可以这样做:

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

在围棋里是这样的:

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 {}
            }
        }
    }
}
uemypmqf

uemypmqf1#

Rust中没有map文字语法,我不知道 * 确切 * 的原因,但我认为有多个数据结构表现得像map(比如BTreeMapHashMap),这会使选择一个变得困难。

生 rust 1.56

现在,许多集合都提供了使用FromInto从数组参数进行转换的功能:

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

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

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

    let s = HashSet::from([1, 2, 3]);
    println!("{:?}", s);

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

    let s = HashMap::from([(1, 2), (3, 4)]);
    println!("{:?}", s);
}

可以将此逻辑 Package 回宏中,以获得一些语法糖:

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

macro_rules! collection {
    // map-like
    ($($k:expr => $v:expr),* $(,)?) => {{
        core::convert::From::from([$(($k, $v),)*])
    }};
    // set-like
    ($($v:expr),* $(,)?) => {{
        core::convert::From::from([$($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);
}

生 rust 1.51

从Rust 1.51开始,你可以使用按值数组迭代器和FromIterator来收集到很多种集合中:

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

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

    // Rust 1.51
    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);
}

请注意,在Rust 1.53中,并不总是需要std::array::IntoIter
可以将此逻辑 Package 回宏中,以获得一些语法糖:

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

macro_rules! collection {
    // map-like
    ($($k:expr => $v:expr),* $(,)?) => {{
        use std::iter::{Iterator, IntoIterator};
        Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*]))
    }};
    // set-like
    ($($v:expr),* $(,)?) => {{
        use std::iter::{Iterator, IntoIterator};
        Iterator::collect(IntoIterator::into_iter([$($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);
}

这些解决方案避免了不必要的分配和重新分配。
另见:

先前版本

您可以创建一个宏来完成这项工作,如Why does this rust HashMap macro no longer work?中所示。下面是稍微简化的宏,它具有足够的结构,使其成为runnable in the playground

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的重新分配。一个更复杂的计算值的宏版本是可能的,但是性能上的好处可能不是大多数宏使用者所能受益的。

nfzehxib

nfzehxib2#

我推荐maplit板条箱。
引用文件:
特定类型容器文本的宏。

use maplit::hashmap;

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

maplit crate使用=>语法进行Map宏。由于常规macro_rules!宏中的语法限制,无法使用:作为分隔符。
注意,rust宏可以灵活地使用方括号进行调用。您可以使用hashmap!{}hashmap![]hashmap!()。此crate建议将{}作为map & set宏的约定,它与它们的调试输出相匹配。

  • btreemap从键值对列表中创建BTreeMap
  • btreeset从元素列表中创建一个BTreeSet
  • hashmap从键值对列表中创建HashMap
  • hashset从元素列表中创建一个HashSet
nafvub8i

nafvub8i3#

documentation for HashMap中有一个如何实现这一点的示例:

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

14ifxucb4#

从Rust 1.56开始,你可以使用from()初始化一个HashMap,这有点像HashMap文字。from()接受一个键值对数组。你可以这样使用它:

use std::collections::HashMap;

fn main() {
    let hashmap = HashMap::from([
        ("foo", 1),
        ("bar", 2)
    ]);
}
yb3bgrhw

yb3bgrhw5#

正如@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();
e3bfsja2

e3bfsja26#

从Rust 1.51开始,IntoIterator是为数组实现的,因此您可以使用from_iter方法创建一个HashMap,而无需克隆:

use std::collections::HashMap;
use std::iter::FromIterator;

// note that this type annotation is required
let mut map: HashMap<_, _> = HashMap::from_iter([("a", 1), ("b", 2), ("c", 3)]);

从Rust 1.56开始(目前是每晚),您可以使用From<[(K, V); N]>实现,它甚至更简洁:

let mut map = HashMap::from([
    ("a", 1),
    ("b", 2),
    ("c", 3),
]);
lx0bsm1f

lx0bsm1f7#

您可以使用velcro crate*,它类似于其他答案中推荐的maplit,但具有更多的集合类型、更好的语法(至少在我看来!)和更多的特性。
假设您希望使用String s而不是&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!{}
            }
        }
    }
};

由于String的构造方式,这有点难看,但是可以通过使用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!{}
            }
        }
    }
};

这并不比Go语言的版本冗长多少。

**全面披露:我是这个箱子的作者。

pwuypxnk

pwuypxnk8#

对于一个元素

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

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

这要归功于Option能够使用into_iter()变成Iterator的实用性。
在真实的代码中,您可能不需要帮助编译器处理以下类型:

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()的操作,但这会更长,可读性更差,而且可能存在一些优化问题,因此我建议不要使用它。

vdzxcuhz

vdzxcuhz9#

我看过很多花哨的解决方案,但我想要一些简单的。为此,这里有一个特点:

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基本上已经使用了它。

相关问题