HashMap 리터럴은 어떻게 만듭니 까?


87

Rust에서 HashMap 리터럴을 어떻게 만들 수 있습니까? 파이썬에서는 그렇게 할 수 있습니다.

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에는 맵 리터럴 구문이 없습니다. 나는 모르는 정확한 이유를,하지만 난 기대 (예 : 모두 같은 행위 maplike하는 여러 데이터 구조가 있다는 사실 그 BTreeMap와는 HashMap) 열심히 하나를 선택하도록 할 것가.

그러나 왜이 녹슬었던 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. 값을 계산하는 더 복잡한 버전의 매크로도 가능하지만 성능상의 이점은 대부분의 매크로 사용에서 이점을 얻을 수있는 것이 아닙니다.


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/… .
DK.

4
아, 수치심. 나는 이것에 대한 Swift 접근 방식을 좋아하는데, 이는 유형에서 리터럴을 추상화합니다. A DictionaryLiteral는 다음을 준수하는 모든 유형을 초기화하는 데 사용할 수 있습니다 ExpressibleByDictionaryLiteral(표준 라이브러리가 그러한 유형을 제공하더라도 Dictionary)
Alexander

48

나는 단풍 나무 상자를 추천한다 .

문서에서 인용하려면 :

특정 유형의 컨테이너 리터럴에 대한 매크로.

use maplit::hashmap;

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

맵릿 크레이트는 =>매핑 매크로에 구문을 사용 합니다. :일반 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 apr

7
물론 런타임 비용이 있지만 다른 상자 종속성을 추가하거나 이해하기 어려운 매크로를 정의하는 것과 관련된 비용도 있습니다. 대부분의 코드는 성능이 중요하지 않으며이 버전은 매우 읽기 쉽습니다.
jupp0r

2
또한 이것은 복제되지 않는 유형에 대해서는 작동하지 않습니다.
Hutch Moore

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다른 답변에서 권장하는 것과 비슷 하지만 더 많은 컬렉션 유형, 더 나은 구문 (적어도 내 의견으로는!) 및 더 많은 기능이 있습니다.

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

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
"저는 Ruby와 Nim이 이미 사용하고있는 본질적으로 좋은 옵션이라고 생각합니다."이것이 어떻게 논쟁인지 이해가되지 않습니다. 루비 나 님이 더 나은 인수 것 왜 그때 그냥 "그들은 어떻게 그 그것의 좋은 그래서"
Stargateur

인수없이 의견은 SO의 대답에 도움이되지 않습니다
Stargateur

2
세부 사항 : 특성을 "Hash"라고 부르는 것은 나쁜 생각입니다. 이미 그 이름을 가진 특성이 있고 녹에서 해싱 작업을 할 때 빠르게 접하게 될 것입니다.
Denys Séguret

2
또한 copied()복제를 구현하지 않는 유형을 허용하지 않거나, 아무것도 복제하지 않도록하거나, 복제 된 유형으로 만 수행 할 수 있도록하려면 최소한 cloned_to_map ()을 사용하여 명시 적으로 지정하는 것이 좋습니다.
Stargateur 2011
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.