게임 개발

List와 Dictionary, 아이템 목록에서는 무엇을 고를까

조회·순회·삭제 비용을 나누어 자료구조를 선택합니다. 중복 ID 처리와 인덱스 갱신까지 포함한 C# 예제로 설명합니다.

CodingJoa 수정 2026.09.22 3분 정도 걸립니다
라벨이 붙은 오래된 목재 카드 목록 서랍장
키를 알면 서랍 하나만 열면 됩니다. Dictionary 가 하는 일이 정확히 이겁니다.

아이템 목록을 만들 때 List부터 선언하는 것은 자연스럽습니다. 화면에 순서대로 보여주기도 쉽고 Inspector에서 편집하기도 편하니까요. 그런데 네트워크에서 받은 ID로 아이템을 자주 찾기 시작하면 매번 목록을 훑는 부분이 눈에 들어옵니다.

이때 전체를 Dictionary로 바꾸기 전에, 필요한 작업을 나눠 보는 편이 좋습니다. 표시 순서와 ID 조회는 서로 다른 요구입니다. 데이터가 적다면 단순한 List로 충분할 수도 있습니다.

빠르다는 말에 어떤 연산인지 붙인다

List는 인덱스 접근과 연속 순회에 편합니다. 끝에 추가하는 비용은 평균적으로 작지만 용량을 늘릴 때 복사가 생길 수 있습니다. 중간에서 삭제하면 뒤의 요소가 이동하므로 ‘삭제가 잦으면 List가 빠르다’고 일반화하면 안 됩니다.

Dictionary는 키 조회에 적합합니다. 평균적으로 상수 시간에 가까운 조회를 기대하지만 해시 계산과 키 비교, 메모리 비용은 존재합니다. 해시가 충돌했다고 서로 다른 키의 값이 잘못 반환되는 것은 아닙니다. 해시 뒤에 키의 동등성도 비교합니다.

필요한 작업먼저 검토할 구조함께 볼 조건
정해진 개수, 인덱스 접근배열배열 요소 자체는 변경 가능
순서 있는 목록, 끝에 추가List중간 삽입·삭제와 용량 증가
ID로 자주 조회Dictionary중복 키 정책과 메모리
먼저 들어온 작업부터 처리Queue무한히 쌓이지 않도록 상한
마지막 작업부터 되돌리기StackUndo 가능한 정보의 보관 범위

정렬된 범위 조회와 공간 충돌 후보 검색도 다른 문제입니다. 일반 이진 탐색 트리, 균형 트리, 공간 분할 트리를 하나로 묶어 항상 O(log n)이라고 설명할 수는 없습니다.

표시 목록에서 조회 인덱스 만들기

다음은 일반 C# 클래스 예제입니다. Unity Inspector 직렬화 예제는 아니며, 외부에서 전달받은 순서 있는 목록을 ID로 조회하는 부분만 다룹니다.

using System;
using System.Collections.Generic;

public sealed class ItemDefinition
{
    public string Id { get; }
    public string Name { get; }

    public ItemDefinition(string id, string name)
    {
        if (string.IsNullOrWhiteSpace(id))
            throw new ArgumentException("Item ID is required.", nameof(id));
        Id = id;
        Name = name;
    }
}

public sealed class ItemIndex
{
    private readonly Dictionary<string, ItemDefinition> byId =
        new Dictionary<string, ItemDefinition>(StringComparer.Ordinal);

    public ItemIndex(IEnumerable<ItemDefinition> items)
    {
        if (items == null)
            throw new ArgumentNullException(nameof(items));

        foreach (ItemDefinition item in items)
        {
            if (item == null)
                throw new ArgumentException("Null item.", nameof(items));
            if (byId.ContainsKey(item.Id))
                throw new ArgumentException("Duplicate item ID: " + item.Id, nameof(items));
            byId.Add(item.Id, item);
        }
    }

    public bool TryFind(string id, out ItemDefinition item)
    {
        if (id == null)
        {
            item = null;
            return false;
        }
        return byId.TryGetValue(id, out item);
    }
}

이 예제는 같은 ID가 두 번 들어오면 초기화 단계에서 오류로 처리합니다. 조용히 마지막 값으로 덮어쓰면 원본 데이터 오류를 늦게 발견할 수 있기 때문입니다. 대소문자를 구분하는 Ordinal 비교를 선택했으므로 potion과 Potion은 다른 키입니다. 게임의 ID 규칙에 맞춰 정해야 합니다.

인덱스를 만들고 나서도 할 일이 남는다

원본 목록에 항목을 추가해도 이 Dictionary에는 자동 반영되지 않습니다. 로딩 후 목록을 고정할지, 변경할 때 인덱스를 다시 만들지, 두 구조를 함께 갱신할지 정해야 합니다. ID를 불변 프로퍼티로 둔 것도 키와 객체의 ID가 어긋나는 상황을 줄이려는 선택입니다.

검증은 존재하는 ID, 없는 ID, 중복 ID, 빈 목록부터 합니다. 그다음 실제 데이터 규모에서 전체 순회와 ID 조회가 얼마나 자주 발생하는지 측정합니다. Dictionary를 만드는 초기 비용까지 포함해야 적은 데이터에서 불필요한 복잡성을 늘리지 않을 수 있습니다.

참고 문서

이 글 공유하기 X Facebook 네이버

이 글은 기술 설명과 참고 자료, 글쓴이의 판단을 담고 있습니다. 예제와 실제 사용 사례의 적용 조건은 본문을 참고해주세요. 도구의 버전·가격·동작은 시간이 지나면 달라질 수 있으니, 따라 하실 때는 공식 문서를 함께 확인해주세요. 자세한 내용은 이용약관에 정리해두었습니다.