Dictionary 는 번호 대신 이름으로 찾는다
Esc

    Dictionary 는 번호 대신 이름으로 찾는다

    –C#자료구조

    배열도 List 도 꺼낼 때 번호를 씁니다. 그런데 우리가 찾고 싶은 것이 번호가 아닐 때가 있습니다.

    아이디로 사용자를 찾거나, 아이템 이름으로 가격을 찾는 일입니다. List 로 하면 이렇게 됩니다.

    List<string> names = new List<string> { "포션", "검", "방패" };
    List<int> prices = new List<int> { 50, 300, 200 };
    
    int found = -1;
    
    for (int i = 0; i < names.Count; i++)
    {
        if (names[i] == "포션")
        {
            found = prices[i];
            break;
        }
    }

    이름 목록과 가격 목록을 같은 번호끼리 짝이 되도록 나란히 들고 있습니다. 둘의 순서가 어긋나면 값이 통째로 뒤섞이는데, 그걸 막아 주는 장치는 없습니다.

    그리고 찾을 때까지 앞에서부터 하나씩 비교합니다. 운이 좋으면 첫 칸, 나쁘면 마지막 칸입니다. 없으면 전부 다 봅니다. 아이템이 만 개면 만 번 비교합니다.

    앞 글에서 배열은 몇 번째 칸이든 찾는 시간이 같다고 했습니다. 번호를 알 때만 그렇습니다. 번호를 모르고 내용으로 찾으면 훑는 수밖에 없습니다.

    이름을 번호로 바꿔 주면, 이름으로도 곧장 찾아갈 수 있습니다.

    Dictionary 는 이렇게 생겼다

    using System.Collections.Generic;
    
    Dictionary<string, int> prices = new Dictionary<string, int>();
    
    prices["포션"] = 50;
    prices["검"] = 300;
    
    Console.WriteLine(prices["포션"]);     // 50

    꺾쇠 안이 두 칸입니다. 앞이 찾을 때 쓰는 것(키), 뒤가 담을 것(값) 입니다. Dictionary<string, int> 는 문자열로 찾아서 정수를 꺼내는 것입니다.

    대괄호 안에 번호 대신 키가 들어갑니다. 생김새는 배열과 비슷한데 안이 전혀 다릅니다.

    배열 · List Dictionary
    대괄호 안 번호 (0부터) 키 (아무 값)
    순서 있다 보장하지 않는다
    없는 것을 꺼내면 범위 밖 예외 키 없음 예외
    중복 같은 값이 여러 칸에 있어도 된다 같은 키는 하나뿐

    이름을 숫자로 바꿔서 자리를 계산한다

    어떻게 훑지 않고 곧장 갈까요.

    앞에서 글자에 번호가 붙어 있다고 했습니다. 문자열은 그 번호들이 줄지어 선 것입니다. 그러니까 문자열도 결국 숫자 덩어리입니다. 그 숫자들을 섞어서 값 하나로 만들면 이름이 숫자가 됩니다. 이걸 해시라고 합니다.

    넣을 때와 찾을 때가 같은 계산입니다. 넣을 때 5번 칸이었으면 찾을 때도 5번 칸입니다. 그래서 찾아다닐 필요가 없습니다.

    정리하면 Dictionary 는 배열 위에 이름을 번호로 바꾸는 계산을 얹은 것입니다. 번호를 우리가 아는 게 아니라 계산해서 얻는다는 것만 다릅니다.

    그래서 들어 있는 것이 열 개든 만 개든 찾는 시간이 거의 같습니다. List 에서 훑던 것과 여기가 다릅니다.

    번호로 꺼내기 내용으로 찾기
    배열 · List 바로 전부 훑는다
    Dictionary (번호가 없다) 바로

    “거의” 라고 쓴 이유가 있습니다. 서로 다른 이름이 같은 칸으로 계산될 수 있습니다. 그때는 그 칸에서 몇 번 더 비교합니다. 드물게 일어나고, 잘 만든 해시는 이걸 최대한 줄입니다.

    없는 키를 꺼내면 터진다

    Console.WriteLine(prices["없는아이템"]);    // KeyNotFoundException

    배열에서 없는 칸을 건드렸을 때와 같습니다. 그 자리에서 멈춥니다.

    미리 확인하거나, 꺼내면서 확인할 수 있습니다.

    if (prices.ContainsKey("포션"))
    {
        Console.WriteLine(prices["포션"]);
    }
    if (prices.TryGetValue("포션", out int price))
    {
        Console.WriteLine(price);
    }

    아래쪽이 낫습니다. 위쪽은 찾는 일을 두 번 합니다. 있는지 보려고 한 번, 꺼내려고 한 번입니다. TryGetValue 는 한 번에 찾아서 있으면 값까지 담아 줍니다.

    돌려주는 것이 값이 아니라 성공했는지 여부라는 점을 봐 두세요. out 이 무엇인지는 메서드에서 다룰 텐데, 지금은 “실패할 수 있는 일은 성공 여부를 돌려주고 결과는 따로 담아 준다” 는 모양만 기억하면 됩니다.

    넣는 방법이 두 가지다

    prices["포션"] = 50;      // 없으면 넣고, 있으면 덮어쓴다
    prices.Add("포션", 50);   // 없으면 넣고, 있으면 예외

    같은 일 같은데 이미 있는 키를 만났을 때가 다릅니다.

    덮어써도 되는 상황이면 대괄호, 중복이 있으면 안 되는 상황이면 Add 를 씁니다. Add 가 터지는 것은 불편한 게 아니라 알려주는 것입니다. 같은 아이디가 두 번 등록되는 것이 버그라면, 조용히 덮어쓰는 쪽이 훨씬 나쁩니다.

    순서를 믿으면 안 된다

    Dictionary 는 넣은 순서를 지켜 주지 않습니다. 자리를 해시로 정하니 넣은 순서와 상관없는 자리에 놓입니다.

    foreach (var pair in prices)
    {
        Console.WriteLine(pair.Key + ": " + pair.Value);
    }

    foreach 는 아직 안 다뤘습니다. 번호를 신경 쓰지 않고 들어 있는 것을 하나씩 꺼내 도는 반복문이라고만 보면 됩니다.

    돌려는 볼 수 있지만 나오는 순서를 기대하면 안 됩니다. 순서가 중요하면 List 를 같이 쓰거나 정렬해서 써야 합니다.

    정리

    • Dictionary 는 번호 대신 키로 찾습니다. 꺾쇠 안이 키 타입, 값 타입 순서입니다.
    • 키를 해시로 숫자를 만들고 그 숫자로 칸을 정합니다. 결국 번호로 찾는 것은 배열과 같습니다.
    • 그래서 몇 개가 들어 있든 찾는 시간이 거의 같습니다. List 에서 훑던 것과 다릅니다.
    • 없는 키를 꺼내면 예외입니다. TryGetValue 는 한 번에 확인하고 꺼냅니다.
    • 대괄호로 넣으면 덮어쓰고, Add 로 넣으면 중복일 때 터집니다.
    • 넣은 순서는 보장되지 않습니다.