C# - List, Tuple, Queue, SortedList, SortedDictionary, SortedSet

List

정렬

  • https://msdn.microsoft.com/ja-jp/library/w56d4y5z(v=vs.110).aspx
  • https://msdn.microsoft.com/ja-jp/library/b0zbh7b6(v=vs.110).aspx
  • https://msdn.microsoft.com/ja-jp/library/234b841s(v=vs.110).aspx
List<UserWeekTotalNetProfitResult> TopPlyser = new List<UserWeekTotalNetProfitResult>();

TopPlyser.Sort(PlayerCompare);

static int PlayerCompare(UserWeekTotalNetProfitResult user1, UserWeekTotalNetProfitResult user2)
{
	if (user1.TotalScore == user2.TotalScore)
	{
		if (user1.Level == user2.Level)
		{
			// UID는 절대 동점이 없음
			if (user1.UID > user2.UID)
			{
				return -1;
			}
			else
			{
				return 1;
			}
		}
		else
		{
			if (user1.Level > user2.Level)
			{
				return -1;
			}
			else
			{
				return 1;
			}
		}
	}
	else
	{
		if (user1.TotalScore > user2.TotalScore)
		{
			return -1;
		}
		else
		{
			return 1;
		}
	}
}

Find

숫자뿐만이 아닌 문자 검색도 기본적으로 가능하다.

IndexOf()

지정된 개체를 검색하고, 전체 List<(Of <(T>)>)에서 처음으로 검색한 개체의 인덱스(0부터 시작)를 반환합니다.

리스트의 모든 내용을 배열로 복사

string[] TemStrs = new string[TypeName.Count];
TypeName.CopyTo(TemStrs);

리스트 간에 복사

list<string> list2 = list1.ToList<string>();

FindIndex

public void DelUserFromLobbyUserList(UInt64 CharCd)
{
     // List에 LobbyUserInfo의 객체가 들어가 있음
     FindIndex(delegate(LobbyUserInfo user) { return user.CharCd == CharCd; } );
}

struct의 멤버 데이터 변경

  • class의 경우 특정 인덱스에 있는 class의 멤버 데이터 변경을 임의접근 []로 변경할 수 있다.
List<class> list = new List<class>();
list.Add(new class(3));
list[0].n = 4;
  • 그러나 struct는 위와 같이 임의 접근을 통해서 값을 변경할 수 없다. 값을 변경하고 싶다면 임시 변수를 만든 후 변경 후 원 데이터를 덮어써야 한다.
struct S
{
	public int F;

	public S(int f)
	{
		F = f;
	}
}

List<S> list = new List<S>();
list.Add(new S(3));

S tmp = list[0];
tmp.F = 16;

list[0] = tmp;

OrderBy

string[] strs = new string[] { "b", "aaaaa", "cc" };

// 오름 차순으로
var query = strs.OrderBy(s => s);

string[] sortedStrs = query.ToArray();

// 결과 { "aaaaa", "b", "cc" }
string[] strs = new string[] { "b", "aaaaa", "cc" };

// 요소의 Length로 오른차순
var query = strs.OrderBy(s => s.Length);
string[] sortedStrs = query.ToArray();

//결과 { "b", "cc", "aaaaa" }
string[] strs = new string[] { "d", "aaaaa", "cc", "b" };

// 문자열 길이로 오른차순. 동률인 경우는 그대로
var query = strs.OrderBy(s => s.Length).ThenBy(s => s);
string[] sortedStrs = query.ToArray();

//결과 { "b", "d", "cc", "aaaaa" }
string[] strs = new string[] { "b", "aaaaa", "cc" };

// Length 크기로 내림 차순
var query = strs.OrderByDescending(s => s.Length);
string[] sortedStrs = query.ToArray();
//결과 { "aaaaa", "cc", "b" }

배열에서 지정한 범위의 요소를 빼내기

  • 출처: http://dobon.net/vb/dotnet/programing/arrayslice.html
  • 새로운 배열을 만들어서 Array.Copy로 복사
int[] ary1 = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] ary2 = new int[5];

// 지정한 범위를 복사한다.
Array.Copy(ary1, 2, ary2, 0, 5);

// ary2는 { 2, 3, 4, 5, 6 }로 된다
  • Buffer.BlockCopy 사용
    • Array.Copy 보다 처리 속도가 빠르다. 단 기본형만 사용할 수 있다.
int[] ary1 = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] ary2 = new int[5];

// 지정한 범위를 복사한다.
Buffer.BlockCopy(ary1, 2 * 4, ary2, 0, ary2.Length * 4);
  • Skip과 Take 사용
    • LINQ를 사용할 수 있는 곳에서만 사용 가능
int[] ary1 = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Skip으로 2개의 요소를 넘은 후 Take로 5개의요소를 얻는다
int[] ary2 = ary1.Skip(2).Take(5).ToArray();


int[] ary1 = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// SkipWhile에서 2미만의 요소를 skip 하고, TakeWhile로 7미만의 요소를 얻는다.
int[] ary2 = ary1.SkipWhile(i => i < 2).TakeWhile(i => i < 7).ToArray();

// ary2는 { 2, 3, 4, 5, 6 }로 된다

컬렉션에서 중복이 아닌 값 추출하기

var a = new[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 };
var list = a.Distinct();

두 개의 컬력션 합병하기

var a = new[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 };
var b = new[] { 0, 2, 4, 6, 8, 10, 12 };
var list = a.Union(b);

두 개의 컬력션 중 공통부분만 구하기

var a = new[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 };
var b = new[] { 0, 2, 4, 6, 8, 10, 12 };
var list = a.Intersect(b);

컬력션 a에서 b에 포함된 요소를 제거하기

var a = new[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 };
var b = new[] { 0, 2, 4, 6, 8, 10, 12 };
var list = a.Except(b);

Tuple

var population = new Tuple<string, int, int, int, int, int, int>(
                           "New York", 7891957, 7781984,
                           7894862, 7071639, 7322564, 8008278);

var primes = Tuple.Create(2, 3, 5, 7, 11, 13, 17, 19);

var number1 = primes.Item1;
var number2 = primes.Item2;

Queue

  • 스레드 세이프하게 데이터 접근 하기
Queue<int> queue = new Queue<int>();
lock (((ICollection)q).SyncRoot)
{
	foreach (int item in q)
	{
		Console.Write("{0} ", item);
	}
}

SortedList

  • 용도: 요소 삽입, 삭제 보다도 검색 쪽이 압도적으로 많은 경우 사용한다.
  • 계산량: 요소의 삽입, 삭제는 O(n), 검색은 O(log n)
  • 내부 구현: 정렬된 배열에 대한 이분검색을 사용한다. 배열은 배열 리스트와 같은 재확보를 한다.
  • 기본 정렬은 오름차순
  • 기본 사용 방법
SortedList<int, int> 정렬된_최대값 = new SortedList<int, int>();
int 구간_최대값_구하기ver2(int removeValue, int inputValue)
{
		// key의 값이 1이면 삭제, 1 보다 크면 1 감속
    if (정렬된_최대값[removeValue] == 1)
    {
        정렬된_최대값.Remove(removeValue);
    }
    else
    {
        --정렬된_최대값[removeValue];
    }

		// 이미 있는 key 라면 값 1 증가, 없다면 추가
    if (정렬된_최대값.ContainsKey(inputValue))
    {
        ++정렬된_최대값[inputValue];
    }
    else
    {
        정렬된_최대값.Add(inputValue, 1);
    }

		// 특정 위치의 값을 얻는다. 가장 마지막 위치의 값을 반환
    return 정렬된_최대값.IndexOfKey(정렬된_최대값.Count - 1);
}
  • 커스텀화한 정렬 함수 사용하기
class UserRankKey
{
    public string UserID;
    public Int64 Score;
}
class UserRankValue
{
    public int Dummy1;
    public int Dummy2;
    public int Dummy3;
}

SortedList<UserRankKey, UserRankValue> RankingList;

class SortedListTestCompare : IComparer<UserRankKey>
{
    public int Compare(UserRankKey x, UserRankKey y)
    {
        if (x.Score < y.Score)
        {
            return -1;
        }
        else if (x.Score > y.Score)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }
}


if (RankingList.ContainsKey(dummyKeyList[i]))
{
    WriteLog("SortedList: 추가 중 동일 키 발견");
}
else
{
	RankingList.Add(dummyKeyList[i], dummyValueList[i]);
}

SortedDictionary<TKey, TValue>

  • 용도: 사전에 많은 영역을 차지하고 싶은 않은 경우나 요소 수가 크게 바뀌므로 사전 확보 양을 정하기 어려운 경우에 사용한다.
    • 키 순서대로 요소를 열겨할 수 있다.
  • 계산량: 요소의 삽입, 검색, 삭제는 O(log n)
  • 내부 구현: 이분검색 트리(레드블랙 트리)

SortedSet

https://msdn.microsoft.com/ja-jp/library/dd395024(v=vs.110).aspx

IComparer

http://dobon.net/vb/dotnet/programing/icomparer.html

정렬 성능 테스트

  • 하드웨어 사양: i7-2600(3.4GHz), 16기가 RAM
  • 시간 단위는 초
Collection 100,000 1,000,000 5,000,000 1,000,000 에서 요소 1개 변경
List 0.03 0.48 3.24 0.35 ~ 0.04
ParallelQuickSort 0.01 0.23 ~0.24 1.4 ~ 1.5 0.21 ~ 0.27
RtRankingLib 0.51 7.1 45 0.0002 이하
  • RtRankingLib: 추가할 때 정렬하므로 한번에 대량의 데이터를 넣을 때는 느림. 그러나 갱신은 엄청나게 빠름
    • https://github.com/jacking75/smrr-server

ParallelQuickSort

ParallelQuickSort<ParallelQuickSortTestUserRank>.Sort(PQSTestRankingList.ToArray(), new PQSTestCompare());


class ParallelQuickSortTestUserRank
{
	public string UserID;
	public Int64 Score;
	public int Dummy1;
	public int Dummy2;
}

List<ParallelQuickSortTestUserRank> PQSTestRankingList;

class PQSTestCompare : IComparer<ParallelQuickSortTestUserRank>
{
	public int Compare(ParallelQuickSortTestUserRank x, ParallelQuickSortTestUserRank y)
	{
		if (x.Score < y.Score)
		{
			return -1;
		}
		else if (x.Score > y.Score)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}
}

//http://elemarjr.net/2012/01/04/parallel-quicksort-em-c/
//https://gist.github.com/ElemarJR/1562593
public static class ParallelQuickSort<T>
{
	public static void Sort(
		T[] input,
		IComparer<T> comparer,
		int maxDepth = 16,
		int minBlockSize = 10000
		)
	{
		InternalSort(input, 0, input.Length - 1,
			comparer,
			0,
			maxDepth,
			minBlockSize);
	}

	private static void InternalSort(
		T[] input,
		int start, int end,
		IComparer<T> comparer,
		int depth,
		int maxDepth,
		int minBlockSize
		)
	{
		if (start >= end) return;
		if (depth > maxDepth || (end - start) < minBlockSize)
		{
			Array.Sort(input, start, end - start + 1, comparer);
		}
		else
		{
			int pivot = PartitionBlock(input, start, end, comparer);

			var left = Task.Factory.StartNew(() =>
				{
					InternalSort(input, start, pivot - 1,
						comparer, depth + 1, maxDepth, minBlockSize);
				});

			var right = Task.Factory.StartNew(() =>
				{
					InternalSort(input, pivot + 1, end,
						comparer, depth + 1, maxDepth, minBlockSize);

				});

			Task.WaitAll(left, right);
		}
	}

	private static void InternalSort2(
		T[] input,
		int start, int end,
		IComparer<T> comparer,
		int depth,
		int maxDepth,
		int minBlockSize
		)
	{
		if (start >= end) return;
		if (depth > maxDepth || (end - start) < minBlockSize)
		{
			Array.Sort(input, start, end - start + 1, comparer);
		}
		else
		{
			int pivot = PartitionBlock(input, start, end, comparer);

			InternalSort2(input, start, pivot - 1,
				comparer, depth + 1, maxDepth, minBlockSize);

			InternalSort2(input, pivot + 1, end,
				comparer, depth + 1, maxDepth, minBlockSize);
		}
	}

	private static int PartitionBlock(
		T[] input,
		int start,
		int end,
		IComparer<T> comparer)
	{
		T pivot = input[start];
		Swap(input, start, end);

		int result = start;
		for (int i = start; i < end; i++)
		{
			if (comparer.Compare(input[i], pivot) <= 0)
			{
				Swap(input, i, result);
				result++;
			}
		}

		Swap(input, result, end);
		return result;
	}

	private static void Swap(T[] input, int first, int second)
	{
		T h = input[first];
		input[first] = input[second];
		input[second] = h;
	}
}

참고


이 글은 2019-02-26에 작성되었습니다.