Как создать литерал HashMap?

87

Как я могу создать литерал HashMap в Rust? В 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 {}
            }
        }
    }
}
Максим Самбурский
источник

Ответы:

90

В Rust нет синтаксиса литерала карты. Я не знаю точной причины, но полагаю, что тот факт, что существует несколько структур данных, которые действуют подобно карте (например, обе BTreeMapи HashMap), затруднит выбор одной.

Однако вы можете создать макрос, который сделает эту работу за вас, как показано в разделе Почему этот макрос 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добавляемых значений as. Возможна более сложная версия макроса, которая подсчитывает значения, но преимущества в производительности, вероятно, не то, от чего выиграет большинство применений макроса.


В ночной версии Rust вы можете избежать как ненужного выделения (и перераспределения!), Так и необходимости в макросе:

#![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/… .
ДК.
4
Ах, позор. Мне нравится подход Swift, который абстрагирует литералы от их типов. A DictionaryLiteralможет использоваться для инициализации любого типа, который соответствует ExpressibleByDictionaryLiteral(даже если стандартная библиотека предлагает один такой тип Dictionary),
Александр
48

Я рекомендую обрешетку из клена .

Цитата из документации:

Макросы для контейнерных литералов определенного типа.

use maplit::hashmap;

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

Ящик maplit использует =>синтаксис для макросов сопоставления. Невозможно использовать в :качестве разделителя из-за синтаксических ограничений в обычных macro_rules!макросах.

Обратите внимание, что макросы ржавчины гибкие, в которых скобки используются для вызова. Вы можете использовать их как hashmap!{}или hashmap![]или hashmap!(). Этот ящик предполагает, что {}в качестве соглашения для &макросов набора карт он соответствует их выводам отладки.

Макросы

  • btreemap Создать BTreeMapиз списка пар "ключ-значение"
  • btreeset Создайте BTreeSetиз списка элементов.
  • hashmap Создать HashMapиз списка пар "ключ-значение"
  • hashset Создайте HashSetиз списка элементов.
Дэвид Дж.
источник
42

В документации дляHashMap :

let timber_resources: HashMap<&str, i32> = [("Norway", 100), ("Denmark", 50), ("Iceland", 10)]
    .iter()
    .cloned()
    .collect();
jupp0r
источник
4
Хотя в данном случае это работает, при использовании чего-то вроде Stringвместо &str.
Shepmaster
7
Конечно, есть затраты на время выполнения, но есть также затраты, связанные с добавлением другой зависимости ящика или с определением трудных для понимания макросов. Большая часть кода не критична к производительности, и я считаю эту версию очень удобочитаемой.
jupp0r
2
Кроме того, это не работает для типов, кроме Clone.
Хатч Мур
10
Этот пример можно настроить так, чтобы избежать этих проблем, используяvec![ (name, value) ].into_iter().collect()
Johannes
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, что рекомендовано в других ответах, но с большим количеством типов коллекций, лучшим синтаксисом (по крайней мере, на мой взгляд!) И большим количеством функций.

Предполагая, что вы хотите использовать Strings, а не &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!{}
            }
        }
    }
};

Что ненамного многословнее, чем версия Go.


* Полное раскрытие: я автор этого ящика.

Питер Холл
источник
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
«Я думаю, что это хороший вариант, так как он, по сути, уже используется Руби и Нимом». Я не понимаю, почему это аргумент? почему Руби или Ним делают это, было бы лучшим аргументом, чем просто «они делают это, и это хорошо»
Stargateur
мнение без аргументов бесполезно в таком ответе
Stargateur
2
Деталь: называть трейт "Hash" - плохая идея: трейт с таким именем уже существует, и вы быстро столкнетесь с ним при работе с хешированием в ржавчине.
Denys Séguret
2
Я бы также посоветовал copied()запретить тип, который не реализует копирование, избегая клонирования ни за что, или, по крайней мере, сделать его явным, используя cloned_to_map (), если вы также хотите иметь возможность делать это только с клонированным типом.
Stargateur