프로그래밍을 하다 보면 중복되지 않는 난수를 생성해야할 때가 있습니다.

그럴때마다 어떻게 해야할까... 이런 생각이 들기도 하는데요!

정말 간단하게 중복되지 않는 난수를 생성하는 방법이 있습니다.

* 속도도 의외로 빠릅니다. (정말 그런가..?)


일단 중복되지 않는 난수를 생성하는 함수의 코드는 다음과 같습니다.

* 이 코드를 사용하시려면 반드시 아래의 네임스페이스들을 추가해야 합니다.

System

System.Collections.Generic

System.Diagnostics


/// <summary>
/// 지정된 숫자의 범위 갯수만큼 중복되지 않는 난수 배열을 생성합니다.<br />
/// 난수 범위: start ~ end (* end - 1 이 아님에 유의)
/// </summary>
/// <param name="start">난수의 시작 값 입니다.</param>
/// <param name="end">난수의 끝 값 입니다.</param>
public static Int32[] GetRandomNumbers(Int32 start, Int32 end) {
    // 시작 인덱스, 종료 인덱스, 숫자 갯수, 반복 조건식 값
    // 그리고 원본 숫자 및 결과 숫자를 저장할 목록(List)을 초기화한다.
    Int32 startIndex        = start > end ? end : start;
    Int32 endIndex            = start > end ? start : end;
    Int32 nCount            = endIndex - startIndex + 1;
    Int32 nLoopCount        = startIndex + endIndex + 1;
    List<Int32> numberList    = new List<Int32>(nCount);
    List<Int32> resultList    = new List<Int32>(nCount);

    // 원본 목록에 start 부터 end 까지의 값을 집어넣는다.
    for ( Int32 i = startIndex; i < nLoopCount; i++ )
        numberList.Add(i);
    
    // Random 클래스로 난수를 만들 때 유의할 점은
    // 코드가 너무 빠르게 실행되면 동일한 값이 나오게 된다.
    // (틱 값 기반으로 난수를 생성하기 때문에)
    // Environment.TickCount 속성을 왜 사용하지 않느냐고 의아해하시는 분이 계실 수 있는데
    // TickCount 속성은 자료형이 Int32 이기 때문에 정확도가 Stopwatch 클래스의 ElapsedTicks 보다 떨어지게 된다. 
    // 그래서 Stopwatch 클래스를 이용하여 시드를 초기화한다.
    Stopwatch sw = new Stopwatch();
    
    // 스톱워치 시작
    sw.Start();
    
    // 원본 목록에 값이 있을 경우 계속 반복한다.
    while ( numberList.Count > 0 ) {
        
        // 흐른 틱 값과 결과 목록의 항목 갯수를 더해서 시드 값을 생성한다.
        Random rGen = new Random((Int32) sw.ElapsedTicks + resultList.Count);
        
        // 0 부터 원본 목록의 항목 갯수까지의 난수를 생성한다.
        Int32 pickedIndex = rGen.Next(0, numberList.Count);
        
        // 원본 목록에서 값을 가져온 다음 결과 목록에 추가하고
        // 가져온 값을 제거한다.
        resultList.Add(numberList[pickedIndex]);
        numberList.RemoveAt(pickedIndex);
    }
    
    // 스톱워치 정지
    sw.Stop();
    
    // 결과 목록을 배열로 만들어서 반환한다.
    return resultList.ToArray();
}


이 코드의 원리는 다음과 같습니다.

  1. 0 부터 갯수 만큼의 값을 원본 목록에 넣습니다. (for 문에서의 array[i] = i 와 같은 개념)
  2. 0 부터 원본 목록의 항목 갯수 사이의 난수를 발생시키고 원본 목록의 "발생된 난수" 의 위치한 값을 결과 목록에 저장한 후 원본 목록에서부터 선택된 항목을 제거합니다.
  3. 원본 목록의 항목의 갯수가 0 이 될때까지 반복합니다.
크게 어려운 것은 없죠?
코드에 주석도 달아놓았으니 잘 이해하실거라 생각합니다~!!

코드 실행 결과 분석:

분석에 사용된 코드는 다음과 같습니다.
* 함수는 위에 있으므로 생략하였습니다. (LINQ 이용하여 계산)

public static void Main(string[] args) {
    Int32[] rndArray = GetRandomNumbers(0, 0xFFFF);
    Int32    overlappedNumberCount = 0;
    for ( Int32 i = 0; i < 0xFFFF; i++ ) {
        var overlappedNumberArray =
            from n in rndArray
            where n == i select n;
        
        if ( overlappedNumberArray.Count() > 1 )
            overlappedNumberCount++;
    }
    
    Console.WriteLine("중복된 숫자 갯수: {0:N0}", overlappedNumberCount);
    Console.ReadKey(true);
}

실행 결과:



유용하게 사용될거라 생각합니다~!!

즐프하세요!


+ Recent posts