이전 포스팅에선 C#과 Python에서 데이터 그룹을 이용하기 위해 배열(Array)와 리스트(List)를 사용하는 방법에 대해 살펴봤다. 이번 포스팅에선 난수 발생을 위한 random 사용과 고려해야 할 점을 다루도록 한다.
Contents
1. Array in C# and List in Python
2. random in C# and Python
3. Shuffling
4. Shuffling in Python
5. Lottery
2. random in C# and Python
C#은 난수 발생기로 Random 클래스를 제공한다. 네임스페이스 System에 속한 클래스이므로 별도의 네임스페이스 선언을 하지 않아도 된다.
2005년 10월 19일자에 네이버 블로그에 포스팅했던 C#에서의 Shuffling 기법에서 Random 클래스 사용시 발생한 문제와 그 해결을 자세히 다루었기 때문에 결론만 말하자면 “프로그램 내에서 Random 클래스의 인스턴스는 단 한번만 생성하고 이를 계속 사용토록 하라”는 것이다.
0부터 100사이 난수를 발생하는 코드 작성을 아래 두 가지 방법으로 비교해보자.
Case 1 : 메소드 호출을 통한 경우 – 인스턴스 반복 생성
위 코드에선 randomPick() 메소드 호출 시마다 Random 클래스의 새로운 인스턴스가 생성된다.
코드 실행 결과를 보면 다음과 유사하게 된다.
Case 2 : 인스턴스를 한번 생성 후 반복 호출을 통한 경우
위 코드는 Random 클래스 인스턴스를 한번 생성 후 해당 인스턴스를 통해 난수를 반복해서 얻는다.
코드 실행 결과를 보면 다음과 유사하게 된다.
두 실행 결과간 차이를 알아보기 힘들다면 아래와 같은 코드와 그 실행 결과를 살펴보자. 아래 코드는 두 방법으로 각각 생성한 난수를 제너릭(generic) 리스트 클래스의 인스턴스에 넣은 후 생성된 난수의 개수를 구하여 콘솔에 출력하는 것이다. 생성된 난수 개수를 구하는 것은 Linq를 이용하였다. private 영역에서 생성한 Random 클래스의 인스턴스(rand1)의 경우는 seed를 제외했는데 seed를 주더라도 유사한 결과를 얻게 된다. 매번 새롭게 인스턴스가 생성되는 rand2 경우 seed를 제외하면 매우 나쁜 결과를 얻게 된다.
위 코드를 실행하면, 생성된 난수 목록을 세미콜론(;)으로 구분하여 보여준 후, 난수 개수를 내림차순으로 보여준다.
이런 현상이 발생하는 원인은 명확하다. 메소드 호출로 Random 클래스 인스턴스 생성 시 할당하는 seed가 나쁘기 때문이다. Random 클래스를 이용 시, 많은 이들이 seed로 ticks를 습관적으로 사용하는데, patch time이 짧은 상태에서 사용하는 것 자체가 문제를 일으킨다. 아래 코드로 이를 확인해보자.
위 코드는 10회 동안 ticks를 호출하여, 원래 리턴 타입인 long 타입 값과 이를 int 타입으로 변환(casting) 한 값을 콘솔로 출력하는 것이다.
즉 메소드 호출 patch 타임이 ticks 값 변화를 선행하기 때문에 발생하는 문제라 할 수 있다. 이를 해결 하는 방법으로 다음과 같이 코드를 수정 할 수 있다. 다만 이렇게 인스턴스를 반복해서 생성하는 것 보다는 인스턴스를 한번 생성 후 난수를 반복해서 생성하는 것이 여러모로 좋다.
C#의 Random 클래스에서 Next() 메소드는 int 타입의 정수를 리턴 한다. Next() 메소드는 Next(), Next(int maxValue), Next(int minValue, int maxValue) 3개 메소드가 오버로딩 되어 있으며, 주의 할 점은 minValue는 포함이나, maxValue는 제외, 즉 Next(n) 일 경우엔 0 ≤ x < n 이고, Next(m, n) 일 경우엔 m ≤ x < n 의 결과를 얻는다. 0과 0.1 사이 double 타입 실수 값을 얻고자 한다면 NextDouble() 메소드를 사용한다.
파이썬에서 random을 이용하기 위해선 random 모듈 불러오기(import)를 먼저 해야 한다. 난수 발생 함수는 C#에서와 마찬가지로 정수형 데이터나 실수형 데이터를 되돌려 주는데 그 종류는 C#에 비해 매우 다양하다. 예를 들어, 정규분포에서 난수 값을 돌려주는 random.normalvariate(mu, sigma)나 가우스 분포에서 되돌려주는 random.gauss(mu, sigma) 지수 분포나 베타 분포 등도 구비되어 있다. 여기서는 기본 함수 중 0과 1 사이 실수 형 난수를 되돌려주는 random.random(), 지정된 값 사이(최대 값 포함) random.uniform(a, b), 연속하는 정수 범위 중 난수를 되돌려주는(최대 값 포함하는) random.randint(a, b)과 일정한 증가 값을 가지는 정수 범위 중 난수를 되돌려주는(최대 값 제외) random.randrange([start], stop, [, step])만 살펴보겠다. 참고로 파이썬 또한 random 모듈 호출 시 seed 값을 주지 않으면 시스템 시간을 seed로 이용한다.
아래 코드는 random.random() 함수를 이용해 50회 반복 시행한 것이다. print() 문의 종료는 기본 값인 줄 바꿈 대신 세미 콜론(;)으로 대신하였다.
발생 한 난수 개수를 얻기 위해 itertool 모듈을 함께 불러와서 리스트 클래스의 빈 인스턴스를 하나 생성 후 리스트에 50개 난수를 넣고 그 개수를 계산했다. 첫 번째 개수 구하는 코드는 [생성된 값, 개수] 리스트로 생성했고, 두 번째 코드는 개수만 다시 리턴토록 했다. itertools.groupby(sorted(li))로 인해 발생 한 난수는 내림차순으로 정렬 된 상태로 출력된다.
다음 코드는 지정된 정수 범위 내 실수형 난수를 발생하는 random.uniform(a, b)로 주의 할 점은 발생하는 실수형 난수 N은 a ≤ N ≤ b 라는 점이다. 단, python documentation(v2.7.1 기준)에 따르면 b 값은 생성 될 수도 있고 아닐 수도 있는데 이는 a + (b – a) * random() 계산 후 소수부의 정수화(rounding) 결과에 따라 달라진다는 점을 주의해야 한다.
지정된 범위 내 정수형 난수를 생성하는 random.randint(a, b)의 경우도 정수형 난수 N은 a ≤ N ≤ b 사이에서 발생한다.
마지막으로 random.randrange([start], stop [, step])으로 발생하는 정수형 난수 N은 stop 에 지정한 값보다 작다. start 생략 시엔 0으로 고정된다. 또한 아래 코드에 생성된 난수 목록에 보이다 싶이, start와 stop 사이 연속하는 값이 아닌 stop 보다 작은 step 배수 값 중에 난수가 발생한다.
이번 포스트에선 C#과 python에서 random을 통해 난수 생성에 대해 살펴봤다. 다음 포스트에선 카드 섞기 혹은 셔플링(shuffling)이라 불리는 기법을 다루도록 한다.
포스팅 잘 보았습니다.
답글삭제많은 도움이 되었어요~ ^^