Làm cách nào để tạo một HashMap theo nghĩa đen?


86

Làm cách nào để tạo một HashMap theo nghĩa đen trong Rust? Trong Python, tôi có thể làm như vậy:

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

Và trong Go như thế này:

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

Câu trả lời:


90

Không có cú pháp chữ bản đồ trong Rust. Tôi không biết lý do chính xác , nhưng tôi cho rằng thực tế là có nhiều cấu trúc dữ liệu hoạt động giống như bản đồ (chẳng hạn như cả hai BTreeMapHashMap ) sẽ khiến cho việc chọn một trở nên khó khăn.

Tuy nhiên, bạn có thể tạo một macro để thực hiện công việc cho mình, như được trình bày trong Tại sao macro HashMap rỉ sét này không còn hoạt động nữa? . Đây là macro được đơn giản hóa một chút và có đủ cấu trúc để làm cho nó có thể chạy được trong sân chơi :

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));
}

Macro này tránh phân bổ một trung gian không cần thiết Vec, nhưng nó không sử dụng HashMap::with_capacityvì vậy có thể có một số phân bổ lại vô ích HashMapkhi các giá trị được thêm vào. Có thể có một phiên bản phức tạp hơn của macro để đếm các giá trị, nhưng lợi ích về hiệu suất có lẽ không phải là thứ mà hầu hết các trường hợp sử dụng macro sẽ được hưởng lợi.


Trong phiên bản Rust hàng đêm, bạn có thể tránh được cả phân bổ không cần thiết (và phân bổ lại!) Cũng như nhu cầu về macro:

#![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);
}

Logic này sau đó cũng có thể được gói lại thành một macro:

#![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);
}

Xem thêm:


6
Cũng có một cái trong grabbag_macrosthùng. Bạn có thể xem nguồn tại đây: github.com/DanielKeep/rust-grabbag/blob/master/grabbag_macros/… .
ĐK.

4
A, xấu hổ. Tôi thích cách tiếp cận của Swift đối với điều này, nó tóm tắt các ký tự từ các loại của chúng. A DictionaryLiteralcó thể được sử dụng để khởi tạo bất kỳ kiểu nào tuân theo ExpressibleByDictionaryLiteral(mặc dù thư viện tiêu chuẩn cung cấp một kiểu như vậy, Dictionary)
Alexander

48

Tôi đề nghị thùng maplit .

Để trích dẫn từ tài liệu:

Macro cho các ký tự vùng chứa với loại cụ thể.

use maplit::hashmap;

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

Thùng maplit sử dụng =>cú pháp cho các macro ánh xạ. Không thể sử dụng :làm dấu phân tách do cú pháp hạn chế trongmacro_rules! macro .

Lưu ý rằng macro gỉ rất linh hoạt trong đó dấu ngoặc bạn sử dụng cho lời kêu gọi. Bạn có thể sử dụng chúng như hashmap!{}hoặc hashmap![]hoặc hashmap!(). Thùng này gợi ý {}như quy ước cho các &macro tập hợp bản đồ , nó khớp với đầu ra Gỡ lỗi của chúng.

Macro

  • btreemap Tạo BTreeMapdanh sách các cặp khóa-giá trị
  • btreeset Tạo một BTreeSetdanh sách các phần tử.
  • hashmap Tạo HashMapdanh sách các cặp khóa-giá trị
  • hashset Tạo một HashSetdanh sách các phần tử.

41

Có một ví dụ về cách đạt được điều này trong tài liệu choHashMap :

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

4
Mặc dù nó hoạt động trong trường hợp này, nó trở nên đắt tiền không cần thiết khi sử dụng một cái gì đó giống như Stringthay thế &str.
Shepmaster

7
Chắc chắn có chi phí thời gian chạy, nhưng cũng có chi phí liên quan đến việc thêm phụ thuộc thùng khác hoặc với việc xác định các macro khó hiểu. Hầu hết mã không quan trọng về hiệu suất và tôi thấy phiên bản này rất dễ đọc.
jupp0r

2
Ngoài ra, điều này không hoạt động đối với các loại không phải là Nhân bản.
Hutch Moore

9
Ví dụ này có thể được điều chỉnh để tránh những vấn đề đó, bằng cách sử dụngvec![ (name, value) ].into_iter().collect()
Johannes

1
@Stargateur vì tôi không tối ưu hóa quá sớm và bạn cũng vậy. Có thể chương trình của bạn sẽ được hưởng lợi từ việc khởi tạo theo nghĩa đen HashMap được tối ưu hóa hơn, nhưng có thể sẽ không. Nếu bạn muốn đạt được hiệu suất tốt nhất, hãy đo lường và tối ưu hóa các phần của chương trình đang đi đúng hướng. Tối ưu hóa nội dung một cách ngẫu nhiên chỉ là một chiến lược tồi. Câu trả lời của tôi sẽ ổn đối với 99,9999% mọi người. Nó có thể đọc được, biên dịch nhanh, không tạo ra các phụ thuộc làm tăng độ phức tạp và tạo ra các lỗ hổng bảo mật tiềm ẩn.
jupp0r

4

Như đã lưu ý bởi @Johannes trong các nhận xét, bạn có thể sử dụng vec![]vì:

  • Vec<T>thực hiện các IntoIterator<T>đặc điểm
  • HashMap<K, V> dụng cụ FromIterator<Item = (K, V)>

có nghĩa là bạn có thể làm điều này:

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

Bạn có thể sử dụng &strnhưng bạn có thể cần chú thích thời gian tồn tại nếu không 'static:

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

Điều này sẽ không đại diện cho chữ vector trong hệ nhị phân sau đó thu thập nó vào một bản đồ băm trong thời gian chạy?
alex elias

1

Bạn có thể sử dụng velcrothùng *. Nó tương tự như maplit, như được đề xuất trong các câu trả lời khác, nhưng với nhiều loại bộ sưu tập hơn, cú pháp tốt hơn (ít nhất là theo ý kiến ​​của tôi!) Và nhiều tính năng hơn.

Giả sử bạn muốn sử dụng Strings hơn là &str, ví dụ chính xác của bạn sẽ giống như sau:

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!{}
            }
        }
    }
};

Đó là một chút xấu xí vì cách Strings được xây dựng. Nhưng nó có thể được làm gọn gàng hơn một chút, mà không cần thay đổi loại khóa, bằng cách sử dụng hash_map_from!nó sẽ tự động thực hiện chuyển đổi:

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!{}
            }
        }
    }
};

Mà không phải là nhiều tiết hơn so với phiên bản Go.


* Tiết lộ đầy đủ: Tôi là tác giả của cái thùng này.


0

Đối với một phần tử

Nếu bạn muốn khởi tạo bản đồ chỉ với một phần tử trên một dòng (và không có đột biến rõ ràng trong mã của bạn), bạn có thể làm:

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

Đây là nhờ vào tính hữu ích của Optionviệc có thể để trở thành một Iteratorsử dụnginto_iter() .

Trong mã thực, bạn có thể không cần trợ giúp trình biên dịch với kiểu:

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);
}

Ngoài ra còn có một cách để xâu chuỗi điều này để có nhiều hơn một phần tử hoạt động giống như vậy Some(a).into_iter().chain(Some(b).into_iter()).collect(), nhưng phần tử này dài hơn, ít đọc hơn và có thể có một số vấn đề về tối ưu hóa, vì vậy tôi khuyên bạn không nên làm điều đó.


-2

Tôi đã thấy một loạt các giải pháp ưa thích, nhưng tôi chỉ muốn một cái gì đó đơn giản. Cuối cùng, đây là một Đặc điểm:

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)
}

Tôi nghĩ đó là một lựa chọn tốt, vì về cơ bản nó đã được sử dụng bởi Ruby và Nim:


2
"Tôi nghĩ nó là một lựa chọn tốt, vì về cơ bản nó đã được Ruby và Nim sử dụng" Tôi không hiểu đây là một lập luận như thế nào? tại sao ruby ​​hoặc Nim làm điều này sẽ là một đối số tốt hơn khi đó chỉ cần "họ làm như vậy là tốt"
Stargateur

một ý kiến ​​mà không có lập luận sẽ không hữu ích trong một câu trả lời SO
Stargateur

2
Một chi tiết: gọi đặc điểm là "Hash" là một ý tưởng tồi: đã có một đặc điểm với tên đó và bạn sẽ nhanh chóng gặp phải nó khi làm việc với băm trong gỉ.
Denys Séguret

2
Tôi cũng khuyên bạn copied()nên không cho phép loại không triển khai bản sao, tránh sao chép không có gì, hoặc ít nhất là làm cho nó rõ ràng bằng cách sử dụng cloned_to_map () nếu bạn cũng muốn có thể làm điều đó với chỉ loại được sao chép.
Stargateur
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.