목표

  • Collections 사용법에 대해 알아보자.

Collection

  • 프로그램을 제작하다보면 대부분 개채의 그룹을 만들려고 할 것이다. (총알이라던가, 가방, 사용자 목록...)
  • 개체를 그룹화 하는 방법은 두가지가 있다.
    • 크기가 정해져있는 배열을 사용하는 방법
    • 가변적인 크기를 가지는 콜렉션을 사용하는 방법

Collection의 종류

  • Collction을 사용하기 위해서는 System.Collections.Generic를 포함해야 한다.
  • 다음은 자주 사용되는 Collection에 대한 설명이다.
클래스 설명
Dictionary<TKey, TValue> Key에 따라 Value에 접근할 수 있는 자료형
List 인덱스로 Value에 접근할 수 있는 자료형
Queue FIFO(First In First Out) 방식의 자료형
Stack<TKey, TValue> IComparer 구현을 기반으로 정렬된 Key/Value를 관리하는 자료형
SortedList LIFO(Last In First Out) 방식의 자료형

List

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollectionData : MonoBehaviour
{
    // 개체 이니셜라이저를 사용하여 개체의 이니셜라이저를 설정할 수 있다.
    List<string> userList = new List<string>()
    { 
        "white", "black" 
    } ;

    // Start is called before the first frame update
    void Start()
    {
        // Add를 사용하여, List의 마지막에 값을 추가할 수 있다.
        userList.Add("red");
        userList.Add("blue");
        userList.Add("greend");

        // Count를 사용하여 List의 길이를 알 수있다.
        // for문을 사용한 리스트 값 확인 방법
        for (int i = 0; i < userList.Count; i++)
        {
            // 인덱스로 List 요소에 접근할 수 있다.
            Debug.Log(string.Format("{0} : {1}", i, userList[i]));
        }

        // Instert를 사용하여 특정 위치에 값을 삽입할 수 있다.
        userList.Insert(0, "yellow");

        // foreach문을 사용한 리스트 값 확인 방법
        int index = 0;
        foreach (var user in userList)
        {
            Debug.Log(string.Format("{0} : {1}", index, user));
            index++;
        }

        // enumerator를 사용한 리스트 값 확인 방법
        index = 0;
        IEnumerator enumerator = userList.GetEnumerator();   
        while (enumerator.MoveNext() && enumerator.Current != null)
        { 
            Debug.Log(string.Format("{0} : {1}", index, enumerator.Current));
            index++;
        }

        // 특정 값 제거
        userList.Remove("red");

        // 특정 위치의 요소를 제거한다.
        userList.RemoveAt(0);

        // 특정 위치부터, n개의 요소를 제거한다.
        userList.RemoveRange(0, 2);

        // 특정 조건의 값을 삭제한다.
        userList.RemoveAll(userId => 
            // userId가 "b"로 시작한다면 true 리턴
            userId.StartsWith("b", System.StringComparison.Ordinal)
            );

        // 리스트 값 모두 제거.  
        userList.Clear();
    } 
}

Dictionary

  • 주의할 점은 이미 추가된 key값과 동일한 key로는 값을 할당할 수 없다.
  • 아주 가끔 이걸 엔진 버그라고 주장하시는 글이 게임 개발 커뮤니티에 올라오기도 하지만, 언어 스펙이다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollectionData : MonoBehaviour
{
    // 개체 이니셜라이저를 사용하여 초기화 할 수있다.
    Dictionary<string, string> urlList = new Dictionary<string, string>()
    {
        {"nhn", "www.nhn.com"},
        {"naver", "www.naver.com"},
        {"kakao", "www.kakao.com"}
    };

    // Start is called before the first frame update
    void Start()
    {
        // Add를 사용하여 값을 넣을 수 있다.
        urlList.Add("daum", "www.daum.com");

           // 특정 키가 이미 포함되어 있는지 알 수 있다.
        if (urlList.ContainsKey("nhn"))
        {
            Debug.Log("contain nhn key!!");
        }

        // 특정 값이 포함되어 있는지 알 수 있다.
        if (urlList.ContainsValue("www.nhn.com"))
        {
            Debug.Log("contain nhn value!!");
        }

        // 키를 기준으로 특정 값을 제거할 수 있다.
        urlList.Remove("naver");

        // Dictionary의 Keys로 요소에 접근할 수 있다.
        foreach (var key in urlList.Keys)
        {
            Debug.Log(string.Format("key : {0}, value : {1}", key, urlList[key]));
        }

        // 이미 포함되어 있는 키값으로는 값을 삽입할 수 없다.
        // ArgumentException : same key has already been added.
        urlList.Add("daum", "www.dadaum.com");
    }

    // Update is called once per frame
    void Update()
    {

    }
}

Queue

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollectionData : MonoBehaviour
{
    // LogObject 클래스
    public class LogObject
    {
        public string LogMessage { get; set; }
        public string LogLevel { get; set; }

        public override string ToString()
        {
            return string.Format("[{0}] {1}", LogLevel, LogMessage);
        }
    }

    Queue<LogObject> messageQueue = new Queue<LogObject>();

    // Start is called before the first frame update
    void Start()
    {    
        // Enqueue를 사용하여 Queue에 메세지 삽입
        SendMessage("Login", "INFO"); 
        SendMessage("Invalid Password", "CRASH"); 
        SendMessage("Logout", "INFO");

        // Peek는 메세지를 Queue에서 빼지 않고, 확인만 한다.
        ShowMessageList();

        // 배열 형태로 변환도 가능하다.
        Queue<LogObject> otherQueue = new Queue<LogObject>(); 
        LogObject[] logs = messageQueue.ToArray();

        // Dequeue를 사용하면 FIFO(메세지를 넣은 순서대로 값이 반환된다.)
        List<LogObject> logObjects = GetMessage(); 

        for (int i = 0; i < logObjects.Count; i++)
        {
            Debug.Log("logObjects : " + logObjects[i]);
        }
    }

    void SendMessage(string message, string logLevel)
    {
        LogObject log = new LogObject();
        log.LogMessage = message;
        log.LogLevel = logLevel;

        messageQueue.Enqueue(log);
    }

    List<LogObject> GetMessage()
    {
        List<LogObject> logList = new List<LogObject>();

        int queueSize = messageQueue.Count;
        for (int i = 0; i < queueSize; i++)
        {
            LogObject log = messageQueue.Dequeue(); 
            logList.Add(log);
        }

        return logList;
    }

    void ShowMessageList()
    {
        int queueSize = messageQueue.Count;
        for (int i = 0; i < queueSize; i++)
        {
            LogObject log = messageQueue.Peek();
            Debug.Log(log);
        }
    }
}

Stack

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollectionData : MonoBehaviour
{
    Stack stack = new Stack();

    // Start is called before the first frame update
    void Start()
    {
        stack.Push(1);
        stack.Push(2);
        stack.Push(3);

        foreach(var v in stack)
        {
            Debug.Log(v);
        }

        //output : 3, 2, 1
    }
}

SortedList

  • 키를 기준으로 정렬된 컬렉션을 유지한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollectionData : MonoBehaviour
{
    SortedList scoreBoard = new SortedList();

    // Start is called before the first frame update
    void Start()
    {
        scoreBoard.Add(15, "minjaeKim");
        scoreBoard.Add(25, "playerA");
        scoreBoard.Add(5, "playerB");
        scoreBoard.Add(-5, "playerC");

        foreach (var key in scoreBoard.Keys)
        {
            // playerC : -5
            // playerB : 5
            // minjaeKim : 15
            // playerA : 25
            Debug.Log(scoreBoard[key] + " : " + key);
        } 
    }
}

Sort

  • 대부분의 Collection에 구현되어 있는 Interface로 값을 정렬하는 기능이 있다.
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

public class CollectionData : MonoBehaviour  
{  
    List<int> score = new List<int>();

    // Start is called before the first frame update
    void Start()
    {
        score.Add(58);
        score.Add(75);
        score.Add(33);

        // 기본 정렬
        score.Sort();

        // 오름 차순 정렬
        score.Sort((a, b) => a.CompareTo(b));

        // 내림 차순 정렬
        score.Sort((a, b) => b.CompareTo(a));

        // 정렬 반전
        score.Reverse();

        // 출력
        enumerator = score.GetEnumerator(); 
        while (enumerator.MoveNext() && enumerator.Current != null)
        {
            Debug.Log(enumerator.Current);
        }
    }   
} 
  • 기본으로 정의된 정렬기능 외에 커스텀 정렬 기능을 사용하기 위해서는 IComparer 혹은 IComparable 인터페이스를 사용한다.

IComparer

  • 개채 2개를 파라미터로 받아 비교할때 사용한다.
using System.Collections;  
using System.Collections.Generic;  
using UnityEngine;  
using System;

public class CollectionData : IComparable  
{  
  public int Score { get; set; }

  public int CompareTo(CollectionData other)
  {
      // 0 : 두 값이 같다.
      // 1 : 비교 대상보다 큰 값을 가지고 있다.
      // -1 : 비교 대상보다 작은 값을 가지고 있다.
      if(Score == other.Score)
      {
          return 0;
      }
      else if (Score > other.Score)
      {
          return 1;
      }
      else
      {
          return -1;
      }
  } 

} 

IComparable

  • 나 자신과 입력받은 객체를 비교할때 사용한다.
using System.Collections;  
using System.Collections.Generic;  
using UnityEngine;

public class ComparClass : Comparer  
{  
  public override int Compare(CollectionData x, CollectionData y)  
  {  
    // 0 : 두 값이 같다.  
    // 1 : 비교 대상보다 큰 값을 가지고 있다.  
    // -1 : 비교 대상보다 작은 값을 가지고 있다.  
    if (x.Score == y.Score)  
    {  
        return 0;  
    }  
    else if (x.Score > y.Score)  
    {  
        return 1;  
    }  
    else  
    {  
        return -1;  
    }  
  }  
}

예제

using System.Collections;  
using System.Collections.Generic;  
using UnityEngine;

public class SampleScripts : MonoBehaviour  
{  
  // Start is called before the first frame update  
  void Start()  
  {  
      CollectionData c1 = new CollectionData();  
      CollectionData c2 = new CollectionData();

    c1.Score = 10;
    c2.Score = 20;

    int result = c1.CompareTo(c2);

    ComparClass comparClass = new ComparClass();
    result = comparClass.Compare(c1, c2); 
  }  
} 

참고

  • 간혹 Collection 순환을 위해 GetEnumerator을 받고난 뒤, 값을 추가하려는 경우가(?) 있을 수 있다.
  • 에러가 발생한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollectionData : MonoBehaviour  
{  
    List<int> score = new List<int>();

    // Start is called before the first frame update
    void Start()
    {
        score.Add(58);
        score.Add(75);
        score.Add(33);

        IEnumerator enumerator = score.GetEnumerator(); 

        // 에러가 발생한다.
        score.Add(55);

        while (enumerator.MoveNext() && enumerator.Current != null)
        {
            Debug.Log(enumerator.Current);
        }
    }   
} 
  • 혹은 foreach로 순환하면서, 값을 삭제하려는 사람도 있다.
  • 에러가 발생한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollectionData : MonoBehaviour
{
    Dictionary<int, int> dataDic = new Dictionary<int, int>();

    // Start is called before the first frame update
    void Start()
    {
        dataDic.Add(0, 58);
        dataDic.Add(1, 75);
        dataDic.Add(2, 33);

        foreach (var key in dataDic.Keys)
        {
            if (key == 1)
            {
                // 에러가 발생한다.
                dataDic.Remove(key);
            }
        }
    }
}

+ Recent posts