Development/C#

[프렉티컬 C#] Dictionary

이쥬딩 2023. 7. 27. 13:34
728x90

HashSet<T> 클래스

- Dictionary<TKey, TValue>와 비슷하지만 키 부분만 저장하고 값은 저장하지 않는다.

- 중복을 허용하지 않는다.

- 요소를 꺼내는 순서는 정해져 있지 않아서 등록한 순서로 나온다는 보장이 없다.

- Add 메서드는 요소가 객체에 추가되면 true를 반환하고 이미 존재하는 경우에는 예외를 발생시키지 않고 false를 반환한다.

Dictionary<TKey, TValue> 제네릭 클랙스

- 해시 테이블이라는 데이터 구조로 만들어진 클래스

- 키와 이 키에 대응하는 값을 여러 개 저장할 수 있는 컬렉션

- 배열이나 리스트와 비교했을 때 키를 사용해 값을 구하므로 속도가 가장 빠르다.

딕셔너리 초기화

var abcDic = new Dictionaray<string, int>() {
    { "abc", 400 },
    { "def", 300 },
    { "ghi", 350 },
    { "jkl", 500 },
    { "mno", 450 },
};

// C# 6.0 이후
var abcDic = new Dictionaray<string, int>() {
    [ "abc"] = 400 ,
    [ "def"] = 300 ,
    [ "ghi"] = 350 ,
    [ "jkl"] = 500 ,
    [ "mno"] = 450 ,
};

사용자 정의형 객체를 값으로 지정하기

사원 코드를 키로 설정해 쉽고 빠르게 Employee 객체를 구할 수 있다.

var employeeDict = new Dictionaray<int, Employee> {
    { 100, new Employee(100, "김순자") },
    { 200, new Employee(200, "오달순") },
    { 300, new Employee(300, "이아주") },
};

딕션너리 요소 추가

// 1번
employeeDict[200] = new Employee(201, "강지순");

// 2번
employeeDict.Add( 200, new Employee(201, "강지순") );

딕션너리 요소 삭제

Remove 메서드

 

Dictionary<TKey,TValue>.Remove 메서드 (System.Collections.Generic)

Dictionary<TKey,TValue>에서 지정한 키가 있는 값을 제거합니다.

learn.microsoft.com

ContainsKey 메서드

Dictionary<TKey,TValue>에 지정한 키가 포함되어 있는지 여부를 확인합니다.

 

Dictionary<TKey,TValue>.ContainsKey(TKey) 메서드 (System.Collections.Generic)

Dictionary<TKey,TValue>에 지정한 키가 포함되어 있는지 여부를 확인합니다.

learn.microsoft.com

딕셔너리에 있는 모든 요소 꺼내기

- foreach문 사용

- foreach로 꺼낼 수 있는 요소의 형은 KeyValuepair<TKey, TValue>형이다.

- 요소를 꺼내는 순서는 정해져 있지 않아서 등록한 순서로 나온다는 보장이 없다.

foreach (var item in dict)
	Console.WriteLine("{0} = {1}", item.Key, item.Value);

딕셔너리에 있는 모든 키 꺼내기

- Keys 속성 사용

- 요소를 꺼내는 순서는 정해져 있지 않아서 등록한 순서로 나온다는 보장이 없다.

foreach (var key in dict.Keys)
	Console.WriteLine(key);

딕셔너리로 변환

ToDictionary 메서드

- IEnumerable<T>에서 Dictionary<TKey,TValue>을 만든다.

- 배열이나 리스트를 딕셔너리로 변환할 수 있다.

 

Enumerable.ToDictionary 메서드 (System.Linq)

IEnumerable<T>에서 Dictionary<TKey,TValue>을 만듭니다.

learn.microsoft.com

class Package
{
    public string Company { get; set; }
    public double Weight { get; set; }
    public long TrackingNumber { get; set; }
}

public static void ToDictionaryEx1()
{
    List<Package> packages =
        new List<Package>
            { new Package { Company = "Coho Vineyard", Weight = 25.2, TrackingNumber = 89453312L },
              new Package { Company = "Lucerne Publishing", Weight = 18.7, TrackingNumber = 89112755L },
              new Package { Company = "Wingtip Toys", Weight = 6.0, TrackingNumber = 299456122L },
              new Package { Company = "Adventure Works", Weight = 33.8, TrackingNumber = 4665518773L } };

    // Create a Dictionary of Package objects,
    // using TrackingNumber as the key.
    Dictionary<long, Package> dictionary =
        packages.ToDictionary(p => p.TrackingNumber);

    foreach (KeyValuePair<long, Package> kvp in dictionary)
    {
        Console.WriteLine(
            "Key {0}: {1}, {2} pounds",
            kvp.Key,
            kvp.Value.Company,
            kvp.Value.Weight);
    }
}

/*
 This code produces the following output:

 Key 89453312: Coho Vineyard, 25.2 pounds
 Key 89112755: Lucerne Publishing, 18.7 pounds
 Key 299456122: Wingtip Toys, 6 pounds
 Key 4665518773: Adventure Works, 33.8 pounds
*/

딕셔너리로부터 다른 딕셔너리 생성

var abcDic = new Dictionaray<string, int>() {
    [ "abc"] = 400 ,
    [ "def"] = 300 ,
    [ "ghi"] = 350 ,
    [ "jkl"] = 500 ,
    [ "mno"] = 450 ,
};

var newDic = abcDic.Where(x => x.Value >= 300)
			.ToDictionaray(a => a.Key, a => a.Value); // 두번째 인수도 필요!

사용자 지정 클래스를 키로 사용하기

- Equals 메서드 GetHashCode 메서드 오버라이드 해야한다.

‣ 솔직히 이해를 못하겠음 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ 누구 좋은 블로그 있어면 추천 좀 여...

- 오버라이드 안 하면 System.Collections.Generic.KeyNotFoundException 예외 발생한다.

class MonthDay
    {
        public int Day { get; private set; }

        public int Month { get; private set; }

        public MonthDay(int month, int day)
        {
            this.Month = month;
            this.Day = day;
        }

		// Equals, GetHashCode 오버라이드 안하면 예외 발생한다.
        // MonthDay끼리 비교한다
        public override bool Equals(object obj)
        {
            var other = obj as MonthDay;
            if (other == null)
                throw new ArgumentException();
            return this.Day == other.Day && this.Month == other.Month;
        }

        // 해시 코드를 구한다
        public override int GetHashCode()
        {
            return Month.GetHashCode() * 31 + Day.GetHashCode();
        }

    }
var dict = new Dictionary<MonthDay, string> {
               { new MonthDay(6, 6), "현충일" },
               { new MonthDay(8, 15), "광복절" },
               { new MonthDay(10, 3), "개천절" },
            };
var md = new MonthDay(8, 15);
var s = dict[md];
Console.WriteLine(s);

키 중복 허용하기

값의 형을 List<>로 지정한다. ➢ 하나의 키에 여러 값을 지정할 수 있게 된다. ➢ 결과적으로 키가 중복되는 것을 허용하는 것이다.

 // 딕셔너리를 초기화한다
var dict = new Dictionary<string, List<string>>() {
   { "PC", new List<string> { "퍼스널 컴퓨터", "프로그램 카운터", } },
   { "CD", new List<string> { "컴팩트 디스크", "캐시 디스펜서", } },
};

// 딕셔너리에 추가한다
var key = "EC";
var value = "전자상거래";
if (dict.ContainsKey(key)) {
    dict[key].Add(value);
} else {
    dict[key] = new List<string> { value };
}

// 딕셔너리의 내용을 열거한다
foreach (var item in dict) {
    foreach (var term in item.Value) {
        Console.WriteLine("{0} : {1}", item.Key, term);
    }
}

/*
[결과]
PC : 퍼스널 컴퓨터 
PC : 프로그램 카운터
CD : 컴팩트 디스크
CD : 캐시 디스펜서
EC : 전자상거래
*/

연습 문제

문제 7.1

    static void Main(string[] args)
    {
      // 1
      var str = "Cozy lummox gives smart squid who asks for job pen";

      var str2 = str.Replace(" ", "").ToUpper();

      var dic = new Dictionary<char, int>();

      foreach (var item in str2)
      {
        if (dic.ContainsKey(item))
        {
          dic[item] = ++dic[item];
        }
        else
        {
          dic[item] = 1;
        }
      }

      foreach (var item in dic.OrderBy(s => s.Key))
      {
        Console.WriteLine($"'{item.Key}' : {item.Value}");
      }

      // 2
      var sortedDic = new SortedDictionary<char, int>(dic);
    }

리뷰

 

1. dic[item] = ++dic[item]; 을 dic[item]++; 로 바꾸면 되는데 너무 복잡하게 작성했다.

예제 코드에서 if ('A' <= uc && uc <= 'Z') {} 구문은 꼭 필요했는지 의문이다.

또, var uc = char.ToUpper(c); 코드도 굳이 반복문 안에 들어가야했는지 의문이다. 

내 코드가 더 나은 듯?

2. 난 SortedDictionary 클래스 안에 내장되어 있는 Convert 기능 사용했는데 예제는 처음부터 다시 작성했네

차이점 보면 Dictionary는 OrderBy 해줘야하지만 SortedDictionary는 자동 정렬되서 출력된다.

그래서 SortedDictionary 군!

검색해서 나온 Dictionary랑 SortedDictionary 차이점

A SortedDictionary is implemented as a binary search tree. Therefore, accessing an element is O(lg(n)). A Dictionary is a hash table, and has a complexity of O(1) for access.

A SortedDictionary is quite useful when you need the data to be sorted (a Dictionary has no defined order). Dictionary is appropriate for most cases.

예제 코드

    class Program {
        static void Main(string[] args) {
            var text = "Cozy lummox gives smart squid who asks for job pen";
            Exercise1_1(text);
            Console.WriteLine();
            Exercise1_2(text);
        }

        static void Exercise1_1(string text) {
            var dict = new Dictionary<Char, int>();
            foreach (var c in text) {
                var uc = char.ToUpper(c);
                if ('A' <= uc && uc <= 'Z') {
                    if (dict.ContainsKey(uc))
                        dict[uc]++;
                    else
                        dict[uc] = 1;
                }
            }
            foreach (var item in dict.OrderBy(x => x.Key))
                Console.WriteLine("{0}:{1}", item.Key, item.Value);
        }

        static void Exercise1_2(string text) {
            var dict = new SortedDictionary<Char, int>();
            foreach (var c in text) {
                var uc = char.ToUpper(c);
                if ('A' <= uc && uc <= 'Z') {
                    if (dict.ContainsKey(uc))
                        dict[uc]++;
                    else
                        dict[uc] = 1;
                }
            }
            foreach (var item in dict)
                Console.WriteLine("{0}:{1}", item.Key, item.Value);
        }
    }

문제 7.2

class Program
{
    ...
    
    // 3번
    var canRemove = abbrs.Remove("IOC");
    Console.WriteLine(canRemove);
    var count = abbrs.Count;
    Console.WriteLine(count);

    // 4번
    abbrs.Find3Char();
    
    ...
}
class Abbreviations
{
        ... 
        
        // 1번
        public int Count => _dict.Count;

        // 2번
        public bool Remove(string abbr)
        {
            if (_dict.ContainsKey(abbr))
                return true;

            return false;
        }

        // 4번
        public void Find3Char()
        {
            var newDict = _dict.Where(w => w.Key.Count() == 3);
            foreach (var item in newDict)
            {
                Console.WriteLine($"{item.Key}={item.Value}");
            }
        }
        
        ...
 }

리뷰

2. Remove 메서드는 요소를 성공적으로 찾아서 제거한 경우 true이고, 그렇지 않으면 false 반환한다. 괜히 if문으로 이중체크 했네!

4. 길이 구하는 걸 때 습관적으로 Count 메서드 쓴다. 3글자로 된 줄임말을 출력하는거니 글자 길이를 확실하게 나타내는 Length 메서드를 사용하는게 좋을 듯 하다.

What 보다는 How를 생각하자!

예제 코드

class Program
{
    ...
    
    // 7.2.3 (Count를 호출한 예)
    // 위에 나온 Add 메서드에서 두 개의 오브젝트를 추가했으므로 읽어들인 단어 수+2가 Count의 값이 된다
    var count = abbrs.Count;
    Console.WriteLine(abbrs.Count);
    Console.WriteLine();

     // 7.2.3 (Remove를 호출한 예)
     if (abbrs.Remove("NPT"))
     	Console.WriteLine(abbrs.Count);
     if (!abbrs.Remove("NPT"))
        Console.WriteLine("삭제할 수 없습니다.");
     Console.WriteLine();

     // 7.2.4
     // IEnumerable<>를 구현했으므로 LINQ를 사용할 수 있다
     foreach (var item in abbrs.Where(x => x.Key.Length == 3))
           Console.WriteLine("{0}={1}", item.Key, item.Value);
    
    ...
}
class Abbreviations
{
        ... 
        
        // 7.2.1
        public int Count {
            get {
                return _dict.Count;
            }
        }

        // 7.2.2
        public bool Remove(string abb) {
            return _dict.Remove(abb);
        }
        
        ...
 }

 

728x90