Wie erstelle ich ein HashMap-Literal?

80

Wie kann ich ein HashMap-Literal in Rust erstellen? In Python kann ich das so machen:

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

Und in Go so:

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 {}
            }
        }
    }
}
Maxim Samburskiy
quelle

Antworten:

85

In Rust gibt es keine Map-Literal-Syntax. Ich kenne den genauen Grund nicht, aber ich gehe davon aus, dass die Tatsache, dass es mehrere Datenstrukturen gibt, die maplike wirken (wie beide BTreeMapund HashMap), es schwierig machen würde, eine auszuwählen.

Sie können jedoch ein Makro erstellen, um die Aufgabe für Sie zu erledigen, wie unter Warum funktioniert dieses rostige HashMap-Makro nicht mehr? . Hier ist das Makro ein wenig vereinfacht und mit genügend Struktur, um es auf dem Spielplatz laufen zu lassen :

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

Dies hat keine zusätzliche Zuordnung und ist maximal effizient.


In einer nächtlichen Version von Rust können Sie sowohl unnötige Zuweisungen als auch die Notwendigkeit eines Makros vermeiden :

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

Diese Logik kann dann auch wieder in ein Makro eingeschlossen werden:

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

Siehe auch:

Shepmaster
quelle
6
Es gibt auch einen in der grabbag_macrosKiste. Sie können die Quelle hier sehen: github.com/DanielKeep/rust-grabbag/blob/master/grabbag_macros/… .
DK.
4
Ah, schade. Ich mag die schnelle Herangehensweise, die Literale von ihren Typen abstrahiert. A DictionaryLiteralkann verwendet werden, um jeden Typ zu initialisieren, der dem entspricht ExpressibleByDictionaryLiteral(obwohl die Standardbibliothek einen solchen Typ anbietet Dictionary)
Alexander - Reinstate Monica
46

Ich empfehle die Maplit- Kiste.

Um aus der Dokumentation zu zitieren:

Makros für Containerliterale mit einem bestimmten Typ.

use maplit::hashmap;

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

Die Maplit-Kiste verwendet die =>Syntax für die Mapping-Makros. Es ist nicht möglich, :als Trennzeichen zu verwenden, da die Einschränkungen in regulären macro_rules!Makros syntaktisch sind .

Beachten Sie, dass Rostmakros flexibel sind, in welchen Klammern Sie den Aufruf verwenden. Sie können sie als hashmap!{}oder hashmap![]oder verwenden hashmap!(). Diese Kiste schlägt {}als Konvention für die Kartensatzmakros &vor, dass sie mit ihrer Debug-Ausgabe übereinstimmt.

Makros

  • btreemap Erstellen Sie ein BTreeMapaus einer Liste von Schlüssel-Wert-Paaren
  • btreeset Erstellen Sie eine BTreeSetaus einer Liste von Elementen.
  • hashmap Erstellen Sie ein HashMapaus einer Liste von Schlüssel-Wert-Paaren
  • hashset Erstellen Sie eine HashSetaus einer Liste von Elementen.
David J.
quelle
38

In der Dokumentation finden SieHashMap ein Beispiel dafür :

let timber_resources: HashMap<&str, i32> = [("Norway", 100), ("Denmark", 50), ("Iceland", 10)]
    .iter()
    .cloned()
    .collect();
jupp0r
quelle
4
Während es in diesem Fall funktioniert, wird es unnötig teuer, wenn Sie etwas wie Stringanstelle von verwenden &str.
Shepmaster
6
Sicher, es gibt Laufzeitkosten, aber es gibt auch Kosten, die mit dem Hinzufügen einer weiteren Kistenabhängigkeit oder dem Definieren schwer verständlicher Makros verbunden sind. Der meiste Code ist nicht leistungskritisch und ich finde diese Version sehr gut lesbar.
jupp0r
2
Darüber hinaus funktioniert dies nicht für Typen, die nicht geklont sind.
Hutch Moore
9
Dieses Beispiel kann optimiert werden, um diese Probleme zu vermeiden, indem Sievec![ (name, value) ].into_iter().collect()
Johannes,
@ jupp0r warum in Rust schreiben, wenn du keine Leistung willst? Ich verstehe das nicht. Sie müssen die Detailimplementierung eines Makros nicht verstehen, um es verwenden zu können.
Stargateur vor
4

Wie von @Johannes in den Kommentaren erwähnt, ist es möglich, Folgendes zu verwenden vec![]:

  • Vec<T>implementiert das IntoIterator<T>Merkmal
  • HashMap<K, V> Geräte FromIterator<Item = (K, V)>

was bedeutet, dass Sie dies tun können:

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

Sie können verwenden, müssen &strjedoch möglicherweise Lebensdauern mit Anmerkungen versehen, wenn dies nicht der Fall ist 'static:

let map: HashMap<&str, usize> = vec![("one", 1), ("two", 2)].into_iter().collect();
Kamil Tomšík
quelle
Wird dies nicht das Vektorliteral in der Binärdatei darstellen und es dann zur Laufzeit in einer Hashmap sammeln?
Alex Elias
Warum funktioniert das nicht nur mit einem Array-Literal?
pqnet
0

Für ein Element

Wenn Sie die Karte mit nur einem Element in einer Zeile (und ohne sichtbare Mutation in Ihrem Code) initialisieren möchten, können Sie Folgendes tun:

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

Dies ist der Nützlichkeit zu verdanken, Optioneine IteratorVerwendung werden zu können into_iter().

In echtem Code müssen Sie dem Compiler wahrscheinlich nicht mit dem Typ helfen:

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

Es gibt auch eine Möglichkeit, dies zu verketten, damit mehr als ein Element so etwas tut. Some(a).into_iter().chain(Some(b).into_iter()).collect()Dies ist jedoch länger, weniger lesbar und weist wahrscheinlich einige Optimierungsprobleme auf. Ich rate daher davon ab.

Stargateur
quelle
-1

Ich habe eine Reihe ausgefallener Lösungen gesehen, aber ich wollte nur etwas Einfaches. Zu diesem Zweck ist hier eine Eigenschaft:

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

Ich denke, es ist eine gute Option, da es im Wesentlichen das ist, was Ruby und Nim bereits verwenden:

Steven Penny
quelle
1
"Ich denke, es ist eine gute Option, da es im Wesentlichen das ist, was Ruby und Nim bereits verwenden." Ich verstehe nicht, wie dies ein Argument ist? Warum Ruby oder Nim dies tun, wäre ein besseres Argument als nur "sie tun das, damit es gut ist"
Stargateur vor
Eine Meinung ohne Argument ist in einer SO-Antwort nicht nützlich
Stargateur vor
1
Ein Detail: Das Merkmal "Hash" zu nennen, ist eine schlechte Idee: Es gibt bereits ein Merkmal mit diesem Namen, und Sie werden es schnell finden, wenn Sie mit Rosthashing arbeiten.
Denys Séguret vor
2
Ich würde auch raten, lieber copied()Typ zu verbieten, der keine Kopie implementiert, das Klonen umsonst zu vermeiden oder es zumindest mit cloned_to_map () explizit zu machen, wenn Sie dies auch nur mit geklontem Typ tun möchten.
Stargateur vor