[rust] 8. 일반적인 컬렉션

About_work·2024년 7월 10일
0

rust

목록 보기
11/16
  • 표준 라이브러리에는 컬렉션 (collection) 이라 불리는 데이터 구조들이 여러 개 포함
  • 대부분의 다른 데이터 타입은 단일한 특정 값을 나타내지만, 컬렉션은 다수의 값을 담을 수 있음
  • 내장된 배열 (build-in array) 이나 튜플 타입과는 달리,
    • 이 컬렉션들이 가리키고 있는 데이터들은 힙에 저장
    • 이는 즉 데이터의 양이 컴파일 타임에 결정되지 않아도 되며
    • 프로그램 실행 중에 늘어나거나 줄어들 수 있다는 의미
  • 각 컬렉션 종류는 서로 다른 크기와 비용을 가지고 있으며, 현재의 상황에 따라 적절한 컬렉션을 선택하는 것은 장시간에 걸쳐 발전시켜야 하는 기술
    • 벡터 (vector): 여러 개의 값을 서로 붙어 있게 저장
    • 문자열 (string): 문자 (character) 의 모음
    • 해시맵 (hash map): 어떤 값을 특정한 키와 연관지어 주도록 해줌
      • 이는 맵 (map) 이라 일컫는 좀 더 일반적인 데이터 구조의, 특정한 구현 형태

1. 백터에 여러 값의 목록 저장하기

  • Vec<T>
  • 메모리에서 모든 값을 서로 이웃하도록 배치하는 단일 데이터 구조에, 하나 이상의 값을 저장
  • 벡터는 같은 타입의 값만을 저장할 수 있음 (어떠한 타입의 값이라도 저장 가능)
  • 파일 내의 텍스트 라인들이나 장바구니의 품목 가격 같은 아이템 목록을 저장하는 상황일 때 유용

1.1. 새 벡터 만들기

    let v: Vec<i32> = Vec::new();
  • 위에서 타입 명시 (type annotation) 가 추가된 것에 주목하세요. 이 벡터에 어떠한 값도 집어넣지 않았기 때문에, 러스트는 저장하고자 하는 요소가 어떤 타입인지 알지 못합니다.
  • 벡터는 제네릭 (generic) 을 이용하여 구현됐습니다.

  • 대부분의 경우는 초깃값들과 함께 Vec<T>를 생성하고, 러스트는 저장하고자 하는 값의 타입을 대부분 유추할 수 있으므로, 이런 타입 명시를 할 필요가 거의 없음
  • vec! 매크로를 제공: 제공된 값들을 저장한 새로운 Vec을 생성
  • 기본 정수형이 i32기 때문에 여기서도 타입은 i32
let v = vec![1, 2, 3];

1.2. 벡터 업데이트 하기

    let mut v = Vec::new();

    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
  • Vec<i32> 타입 명시를 붙이지 않아도 되는 이유는, 집어넣은 숫자가 모두 i32 타입인 점을 통하여 러스트가 v의 타입을 추론하기 때문

1.3. 벡터 요소 읽기

  • 벡터에 저장된 값을 참조하는 방법은 인덱싱과 get 메서드 두 가지
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2];
    println!("The third element is {third}");

    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }
  • &와 []를 사용하면 인덱스 값에 위치한 요소의 참조자를 얻게 됩니다.
  • get 함수에 인덱스를 매개변수로 넘기면, match를 통해 처리할 수 있는 Option<&T>를 얻게 됩니다.
  • 두 가지 제공하는 이유는
    • 벡터에 없는 인덱스 값을 사용하고자 했을 때 프로그램이 어떻게 동작할 것인지 선택할 수 있도록 하기 위해서
  • 프로그램에 유효한 참조자가 있다면, 대여 검사기 (borrow checker) 가 (4장에서 다루었던) 소유권 및 대여 규칙을 집행하여
    • 이 참조자와 벡터의 내용물로부터 얻은 다른 참조자들이 계속 유효하게 남아있도록 보장
    let mut v = vec![1, 2, 3, 4, 5];

    let first = &v[0];

    v.push(6);

    println!("The first element is: {first}");

// error
// cannot borrow `v` as mutable because it is also borrowed as immutable
  • 벡터는 모든 요소가 서로 붙어서 메모리에 저장됩니다.
  • 그리고 새로운 요소를 벡터 끝에 추가할 경우,
    • 현재 벡터 메모리 위치에 새로운 요소를 추가할 공간이 없다면,
    • 다른 넉넉한 곳에 메모리를 새로 할당하고 기존 요소를 새로 할당한 공간에 복사
  • 이 경우, 기존 요소의 참조자는 해제된 메모리를 가리키게 되기 때문에, 이러한 상황을 대여 규칙으로 막아둔 것이죠.

1.4. 벡터 값에 대해 반복하기

  • for 루프를 사용하여 i32의 벡터에 있는 각 요소에 대한 불변 참조자를 얻어서 이를 출력하는 방법을 보여줍니다:
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{i}");
    }
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
  • 가변 참조자가 가리키는 값을 수정하려면, += 연산자를 쓰기 전에 * 역참조 연산자로 i의 값을 얻어야 합니다.
    • 역참조 연산자는 15장 ‘포인터를 따라가서 값 얻기’에서 자세히 알아볼 예정입니다.

  • 벡터에 대한 반복 처리는 불변이든 가변이든 상관없이 대여 검사 규칙에 의해 안전합니다.
  • 만일 예제 8-7과 예제 8-8의 for 루프 본문에서 아이템을 추가하거나 지우는 시도를 했다면 예제 8-6의 코드에서 본 것과 유사한 컴파일 에러가 발생하게 됩니다.
  • for 루프가 가지고 있는 벡터에 대한 참조자는 전체 벡터에의 동시다발적 수정을 막습니다.

1.5. 열거형을 이용해 여러 타입 저장하기

  • 다행히도, 열거형의 배리언트는 같은 열거형 타입 내에 정의가 되므로, 벡터 내에 다른 타입의 값들을 저장할 필요가 있다면 열거형을 정의하여 사용할 수 있습니다!
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
  • 러스트가 컴파일 타임에 벡터 내에 저장될 타입이 무엇인지 알아야 하는 이유는,
    • 각 요소를 저장하기 위해 얼마만큼의 힙 메모리가 필요한지 알아야 하기 때문
  • 런타임에 프로그램이 벡터에 저장할 모든 타입 집합을 알지 못하면, 열거형을 이용한 방식은 사용할 수 없을 것입니다.
    • 대신 트레이트 객체 (trait object) 를 이용할 수 있는데, 이건 17장에서 다룰 예정
  • 표준 라이브러리의 Vec에 정의된 유용한 메서드들이 많이 있으니, API 문서를 꼭 살펴봐 주시기 바랍니다.
  • 예를 들면, push에 더해서, pop 메서드는 제일 마지막 요소를 반환하고 지워줍니다.

1.6. 벡터가 버려지면 벡터의 요소도 버려집니다.

  • 벡터는 스코프를 벗어날 때 해제
  • 벡터가 버려질 때 벡터의 내용물도 전부 버려짐
    {
        let v = vec![1, 2, 3, 4];

        // v를 가지고 작업하기
    } // <- 여기서 v가 스코프 밖으로 벗어나고 해제됩니다

2. 문자열에 UTF-8 텍스트 저장하기

  • &str (문자열 슬라이스)

    • 문자열 슬라이스: 문자열(String)의 일부분을 참조
      • 문자열 데이터의 위치와 길이를 저장하여, 원본 문자열 데이터를 참조
      • 러스트 핵심 기능 중 문자열 타입은 오직 하나뿐
      • UTF-8으로 인코딩되어 다른 어딘가에 저장된 문자열 데이터의 참조자
      • let slice = &s[0..3]
    • 문자열 리터럴 (고정된 텍스트)
      • let s = "Hello";
      • 문자열 리터럴은 문자열 슬라이스에 포함됩니다.
      • 프로그램의 바이너리 결과물 안에 저장되어 있으며, 그러므로 문자열 슬라이스
  • 문자열이 컬렉션 장에 있는 이유는

    • 문자열이 바이트의 컬렉션으로 구현되어 있고,
    • 이 바이트들을 텍스트로 통역할 때 유용한 기능을 제공하는 여러 메서드들을 구현해 두었기 때문
  • 사람과 컴퓨터가 String 데이터를 통역하는 방식 간의 차이로 인해 생기는 String 인덱싱의 복잡함을 논의해 보자.

  • 문자열 (String)

    • heap에 저장되는 가변 문자열
    • String은 str 데이터를 소유(포함)
    • String 타입은 언어의 핵심 기능에 구현된 것이 아니고 러스트의 표준 라이브러리를 통해 제공되며,
    • 커질 수 있고, 가변적이며, 소유권을 갖고 있고, UTF-8으로 인코딩된 문자열 타입
let mut s = String::from("Hello");
s.push_str("How are you?");

2.1. 문자열이 뭔가요?

  • 러스타시안들이 ‘문자열’에 대해 이야기할 때는 보통 String과 문자열 슬라이스 &str 타입 둘 중 무언가를 이야기하는 것이지, 특정한 하나를 뜻하는 것은 아닙니다.
  • 이번 절은 대부분 String에 관한 것이지만, 두 타입 모두 러스트 표준 라이브러리에서 매우 많이 사용되며
  • String과 문자열 슬라이스 모두 UTF-8으로 인코딩되어 있습니다.

2.2. 새로운 문자열 생성하기

  • String이 실제로 바이트 벡터에 더하여 몇 가지 보장, 제한, 기능들을 추가한 래퍼 (wrapper) 로 구현되어 있기 때문
    let mut s = String::new();
  • 종종 시작 지점에서 저장해 둘 문자열의 초깃값을 가지고 있을 것
  • 그럴 때는 to_string 메서드를 이용하는데, 이는 Display 트레이트가 구현된 어떤 타입이든 사용 가능하며, 문자열 리터럴도 이 트레이트를 구현하고 있습니다.
    let data = "initial contents"; // 문자열 리터럴

    let s = data.to_string();

    // 이 메서드는 리터럴에서도 바로 작동합니다:
    let s = "initial contents".to_string();
    let s = String::from("initial contents");

2.3. 문자열 업데이트 하기

  • + 연산자format! 매크로를 사용하여 편리하게 String 값들을 이어붙일 수 있습니다.

2.3.1. push_str과 push를 이용하여 문자열 추가하기

    let mut s = String::from("foo");
    s.push_str("bar");
  • 위의 두 줄이 실행된 후 s에는 foobar가 들어있을 것입니다.
  • push_str 메서드는 문자열 슬라이스를 매개변수로 갖는데, 이는 매개변수의 소유권을 가져올 필요가 없기 때문
    let mut s1 = String::from("foo");
    let s2 = "bar";
    s1.push_str(s2);
    println!("s2 is {s2}");
  • 만일 push_str 함수가 s2의 소유권을 가져갔다면, 마지막 줄에서 이 값을 출력할 수 없었을 것입니다. 하지만 이 코드는 기대했던 대로 작동합니다!

  • push 메서드는 한 개의 글자를 매개변수로 받아서 String에 추가

    let mut s = String::from("lo");
    s.push('l');

2.3.2. + 연산자나 format! 매크로를 이용한 접합

  • 가지고 있는 두 개의 문자열을 조합하고 싶은 경우도 종종 있습니다.
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // s1은 여기로 이동되어 더 이상 사용할 수 없음을 주의하세요
  • + 연산자는 add 메서드를 사용하는데, 이 메서드의 시그니처는 아래처럼 생겼습니다:
fn add(self, s: &str) -> String {
  • 표준 라이브러리에는 add가 제네릭과 연관 타입을 사용하여 정의되어 있음
  • 여기서는 제네릭에 구체 타입 (concrete type) 을 대입하였고, 이는 String 값으로 이 메서드를 호출했을 때 발생
  • &s2를 add 호출에 사용할 수 있는 이유: &String 인수가 &str로 강제될 수 있기 때문
  • add 함수가 호출되면, 러스트는 역참조 강제 변환 (deref coercion) 을 사용하는데, 이것이 add 함수 내에서 사용되는 &s2를 &s2[..]로 바꿉니다.
    • 역참조 강제 변환은 15장에서 더 자세히 다루겠습니다.
  • 시그니처에서 add가 self의 소유권을 가져가는 것을 볼 수 있는데, 이는 self가 &를 안 가지고 있기 때문
    • 즉 위 예제에서, s1이 add 호출로 이동되어 이후에는 더 이상 유효하지 않을 것이라는 의미
  • 따라서 let s3 = s1 + &s2;가 마치 두 문자열을 복사하여 새로운 문자열을 만들 것처럼 보일지라도,
    • 실제로 이 구문은 s1의 소유권을 가져다가 s2의 내용물의 복사본을 추가한 다음, 결과물의 소유권을 반환
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = s1 + "-" + &s2 + "-" + &s3;
  • 이 시점에서 s는 tic-tac-toe가 될 것입니다. +와 " 문자가 많으면 어떤 결과가 나올지 확인이 어렵습니다.
  • 더 복잡한 문자열 조합에는 대신 format! 매크로를 사용할 수 있습니다:
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{s1}-{s2}-{s3}");
  • format!을 이용한 버전이 훨씬 읽기 쉽고,
  • format! 매크로로 만들어진 코드는 참조자를 이용하므로 이 호출은 아무 매개변수의 소유권도 가져가지 않습니다.

2.4. 문자열 내부의 인덱싱

  • 러스트에서 인덱싱 문법을 이용하여 String의 부분에 접근하고자 하면 에러를 얻게 됩니다.
    let s1 = String::from("hello");
    let h = s1[0];
// error[E0277]: the type `String` cannot be indexed by `{integer}`
  • 러스트가 문자열을 메모리에 저장하는 방법에 대해 설명해야 합니다.

2.4.1. 내부적 표현

  • String은 Vec<u8>을 감싼 것
    let hello = String::from("Hola");
  • 이 경우 len은 4가 되는데, 이는 문자열 ‘Hola’를 저장하고 있는 Vec이 4바이트 길이라는 뜻
  • UTF-8으로 인코딩되면 각각의 글자들이 1바이트씩 차지한다는 것이죠.
    let hello = String::from("Здравствуйте");
  • 길이가 24 (8이 아님)
  • 문자열의 바이트 안의 인덱스는, 유효한 유니코드 스칼라 값과 항상 대응되지는 않을 것
let hello = "Здравствуйте";
let answer = &hello[0];
  • UTF-8으로 인코딩될 때, З의 첫 번째 바이트는 208이고, 두 번째는 151이므로, answer는 사실 208이 되어야 하지만, 208은 그 자체로는 유효한 문자가 아닙니다.

2.4.2. 바이트와 스칼라 값과 문자소 클러스터! 이런!

  • 바이트, 유니코드 스칼라 값(러스트의 char 타입), 그리고 문자소 클러스터 (grapheme cluster, 우리가 글자라고 부르는 것과 가장 근접한 것) 입니다.
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]
  • 이건 18바이트이고 컴퓨터가 이 데이터를 궁극적으로 저장하는 방법입니다.
  • 만일 이를 유니코드 스칼라 값, 즉 러스트의 char 타입인 형태로 본다면, 아래와 같이 보이게 됩니다:
['न', 'म', 'स', '्', 'त', 'े']
  • 여섯 개의 char 값이 있지만, 네 번째와 여섯 번째는 글자가 아닙니다:
  • 그 자체로는 이해할 수 없는 발음 구별 부호
  • 마지막으로, 이 문자열을 문자소 클러스터로 본다면, 이 힌디 단어를 구성하는 네 글자를 알아낼 수 있습니다:
["न", "म", "स्", "ते"]
  • 러스트가 String을 인덱스로 접근하여 문자를 얻지 못하도록 하는 마지막 이유는,
    • 인덱스 연산이 언제나 상수 시간(O(1))에 실행될 것으로 기대받기 때문입니다.
  • 그러나 String을 가지고 그러한 성능을 보장하는 것은 불가능한데,
    • 그 이유는 러스트가 문자열 내에 유효한 문자가 몇 개 있는지 알아내기 위해 내용물을 시작 지점부터 인덱스로 지정된 곳까지 훑어야 하기 때문

2.5. 문자열 슬라이싱하기

  • []에 숫자 하나를 사용하는 인덱싱이 아니라,
  • []와 범위를 사용하여 특정 바이트들이 담고 있는 문자열 슬라이스를 만들 수 있습니다:
let hello = "Здравствуйте";

let s = &hello[0..4];
  • 만약에 &hello[0..1]처럼 문자 바이트의 일부를 슬라이스를 얻으려고 한다면, 러스트는 벡터 내에 유효하지 않은 인덱스에 접근했을 때와 동일한 방식으로 런타임에 패닉을 발생시킬 것입니다.
  • 범위를 지정하여 문자열 슬라이스를 생성하는 것은 프로그램을 죽게 만들 수도 있기 때문에 주의깊게 사용되어야 합니다.

2.6. 문자열에 대한 반복을 위한 메서드

  • 문자열 조각에 대한 연산을 하는 가장 좋은 방법은 아래 2가지 중 명시적으로 지정하는 것
    • 문자를 원하는 것인지
    • 바이트를 원하는 것인지
  • (개별적인 유니코드 스칼라 값에 대해서는 chars 메서드를 사용하세요.)
  • ‘Зд’에 대해 chars 함수를 호출하면 각각을 분리하여 char 타입의 두 개의 값을 반환하고, 이 결과에 대한 반복을 통하여 각 요소에 접근할 수 있습니다:
for c in "Зд".chars() {
    println!("{c}");
}

З
д

다른 방법으로 bytes 메서드는 각 원시 바이트를 반환하는데, 문제의 도메인이 무엇인가에 따라 적절할 수도 있습니다:

for b in "Зд".bytes() {
    println!("{b}");
}

위의 코드는 이 문자열을 구성하는 네 개의 바이트를 출력합니다:

208
151
208
180
  • 데바나가리 문서와 같은 문자열로부터 문자소 클러스터를 얻는 방법은 복잡해서, 이 기능은 표준 라이브러리를 통해 제공되지 않습니다.

2.7. 문자열은 그렇게 단순하지 않습니다

  • 좋은 소식은 표준 라이브러리에 이런 복잡한 상황을 올바르게 처리하는 데 도움이 될 String 및 &str 타입 기반의 기능을 다양하게 제공한다는 점입니다.
  • 문자열 검색을 위한 contains와 문자열 일부를 다른 문자열로 바꾸는 replace 같은 유용한 메서드들에 대해 알아보려면 꼭 문서를 확인해 보세요.

3. 해시맵에 서로 연관된 키와 값 저장하기

  • HashMap<K, V> 타입: K 타입의 키V 타입의 값에 대해 해시 함수 (hashing function) 를 사용하여 매핑한 것을 저장
    • 이 해시 함수는 이 키와 값을 메모리 어디에 저장할지 결정
  • 임의의 타입으로 된 키를 이용하여 데이터를 찾고 싶을 때 유용
  • 해시맵의 기본 API를 다룰 것이지만, 표준 라이브러리의 HashMap에 정의되어 있는 함수 중에는 더 많은 좋은 것들이 숨어있습니다.
  • 항상 말했듯이, 더 많은 정보를 원하신다면 표준 라이브러리 문서를 확인하세요.

3.1. 새로운 해시맵 생성하기

  • 빈 해시맵을 생성하는 한 가지 방법으로 new를 사용한 뒤 insert를 이용하여 요소를 추가하는 것
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
  • 먼저 표준 라이브러리의 컬렉션 부분으로부터 HashMap을 use로 가져와야 할 필요가 있음을 주목하세요.
    • 이 장에서 보고 있는 세 가지 일반적인 컬렉션 중 해시맵이 제일 덜 자주 사용되는 것이기 때문에, 프렐루드의 자동으로 가져오는 기능에는 포함되어 있지 않습니다.
  • 또한 해시맵은 표준 라이브러리로부터의 지원을 덜 받습니다.
  • 해시맵도 데이터를 힙에 저장
  • 모든 키는 서로 같은 타입이어야 하고, 모든 값도 같은 타입이여야 합니다.

3.2. 해시맵의 값 접근하기

  • get 메서드에 키를 제공하여 해시맵으로부터 값을 얻음
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    let team_name = String::from("Blue");
    let score = scores.get(&team_name).copied().unwrap_or(0);
  • get 메서드는 Option<&V>를 반환
  • copied를 호출하여 Option<&i32>가 아닌 Option<i32>를 얻어온 다음,
  • unwrap_or를 써서 scores가 해당 키에 대한 아이템을 가지고 있지 않을 경우 score에 0을 설정하도록 처리
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    for (key, value) in &scores {
        println!("{key}: {value}");
    }

3.3. 해시맵과 소유권

  • i32처럼 Copy 트레이트를 구현한 타입의 값은 해시맵 안으로 복사됨
  • String처럼 소유권이 있는 값의 경우, 아래의 예제 8-22와 같이 값들이 이동되어 해시맵이 그 값의 소유자가 됩니다:
    use std::collections::HashMap;

    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // field_name과 field_value는 이 시점부터 유효하지 않습니다.
    // 사용을 시도해보고 무슨 컴파일러 에러가 발생하는 알아보세요!
  • insert를 호출하여 field_name과 field_value를 해시맵으로 이동시킨 후에는 더 이상 이 둘을 사용할 수 없음
  • 해시맵에 값들의 참조자들을 삽입한다면, 이 값들은 해시맵으로 이동되지 않을 것
  • 하지만 참조자가 가리키고 있는 값은 해시맵이 유효할 때까지 계속 유효해야 함

3.4. 해시맵 업데이트하기

  • 각각의 유일한 키는 연관된 값을 딱 하나만 가질 수 있습니다.

  • (그 역은 성립하지 않습니다: 예를 들면 블루 팀과 옐로 팀 모두 scores 해시맵에 10점을 저장할 수도 있습니다.)

  • 해시맵의 데이터를 변경하고 싶을 때는 키에 이미 값이 할당되어 있을 경우에 대한 처리 방법을 결정해야 합니다.

    • 예전 값을 완전히 무시하면서 새 값으로 대신할 수도 있습니다.
    • 혹은 예전 값을 계속 유지하면서 새 값은 무시하고, 해당 키에 값이 할당되어 있지 않을 경우에만 새 값을 추가하는 방법을 선택할 수도 있습니다.
    • 또는 예전 값과 새 값을 조합할 수도 있습니다.

3.4.1. 값을 덮어쓰기

    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Blue"), 25);

    println!("{:?}", scores);
  • 이 코드는 {"Blue": 25}를 출력할 것입니다. 원래의 값 10은 덮어써졌습니다.

3.4.2. 키가 없을 때만 키와 값 추가하기

  • 만일 키가 해시맵 내에 존재하면, 해당 값은 그대로 둬야 합니다.
  • 만일 키가 없다면, 키와 그에 대한 값을 추가합니다.
  • entry 함수의 반환 값은 열거형 Entry인데, 해당 키가 있는지 혹은 없는지를 나타냅니다.
    use std::collections::HashMap;

    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50);

    println!("{:?}", scores);
  • or_insert 메서드는 해당 키가 존재할 경우 Entry 키에 대한 연관된 값을 반환하도록 정의되어 있고,
  • 그렇지 않은 경우 매개변수로 제공된 값을 해당 키에 대한 새 값으로 삽입하고, 수정된 Entry에 대한 값을 반환합니다.

3.4.3. 예전 값에 기초하여 값을 업데이트하기

  • 예를 들어, 예제 8-25는 어떤 텍스트 내에 각 단어가 몇 번이나 나왔는지를 세는 코드를 보여줍니다.
    use std::collections::HashMap;

    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}", map);
  • 해시맵에 대한 반복 처리가 임의의 순서로 일어난다.
  • split_whitespace 메서드는 text의 값을 공백문자로 나눈 서브 슬라이스에 대한 반복자를 반환
  • or_insert 메서드는 실제로는 해당 키에 대한 값의 가변 참조자(&mut V)를 반환
  • 여기서는 count 변수에 가변 참조자를 저장하였고, 여기에 값을 할당하기 위해 먼저 애스터리스크(*)를 사용하여 count를 역참조해야 합니다.
  • 가변 참조자는 for 루프의 끝에서 스코프 밖으로 벗어나고, 따라서 모든 값의 변경은 안전하며 대여 규칙에 위배되지 않습니다.

3.5. 해시 함수

  • HashMap은 해시 테이블과 관련된 서비스 거부 공격 (Denial of Service(DoS) attack) 에 저항 기능을 제공할 수 있는 SipHash라 불리는 해시 함수를 사용합니다.
  • 이는 사용할 수 있는 가장 빠른 해시 알고리즘은 아니지만, 성능을 떨어트리면서 더 나은 보안을 취하는 거래는 가치가 있습니다.
  • 만일 여러분의 코드를 프로파일링해보니 기본 해시 함수가 여러분의 목적에 사용되기엔 너무 느리다면, 다른 해시어를 지정하여 다른 함수로 바꿀 수 있습니다.
  • 해시어 (hasher) 는 BuildHasher 트레이트를 구현한 타입을 말합니다.

profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글