랜덤

랜덤 에서 Python

{other: "%{count}개의 연습 문제"}

랜덤 소개

많은 프로그램은 실제 세상의 사건을 시뮬레이션하기 위해 (겉보기에) 무작위한 값이 필요해요.

흔히 접하는 익숙한 예로는 이런 것들이 있어요.

  • 동전 던지기: ('Heads', 'Tails')에서 무작위로 고른 값.
  • 주사위 굴리기: 1부터 6까지의 무작위 정수.
  • 카드 덱 섞기: 카드 목록의 무작위 순서.

컴퓨터로 진짜 무작위한 값을 만들어 내는 것은 놀랄 만큼 어려운 기술적 과제라서, 이런 결과를 "의사 난수"라고 부르는 걸 볼 수 있어요.

실제로는 파이썬 표준 라이브러리의 random 모듈처럼 잘 설계된 라이브러리는 빠르고 유연하며, 모델링, 시뮬레이션, 게임 등 대부분의 분야에 충분히 좋은 결과를 줘요.

이 페이지의 나머지 부분에서는 random에서 가장 흔히 쓰이는 함수 몇 가지를 살펴봐요. 여기서 다루는 것보다 훨씬 많은 선택지가 있으니, random 문서 전체를 살펴보길 권해요.

Caution

random 모듈은 보안이나 암호 관련 응용 분야에 절대 사용하면 안 돼요.

대신 파이썬은 secrets 모듈을 제공해요. 이 모듈은 암호 보안을 위해 특별히 최적화되어 있어요. secrets 모듈을 만들게 된 몇 가지 기존 문제와 이유는 PEP 506에서 확인할 수 있어요.

가져오기

random 모듈의 도구를 사용하려면 먼저 모듈을 가져와야 해요.

>>> import random

# Choose random integer from a range
>>> random.randrange(1000)
360

>>> random.randrange(-1, 500)
228

>>> random.randrange(-10, 11, 2)
-8

# Choose random integer between two values (inclusive)
>>> random.randint(5, 25)
22

모듈 이름을 매번 입력하기 번거롭다면, 특정 함수를 이름으로 직접 가져올 수 있어요.

>>> from random import choice, choices

# Using choice() to pick Heads or Tails 10 times
>>> tosses = []
... for side in range(10):
...    tosses.append(choice(['H', 'T']))    

>>> print(tosses)
['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']


# Using choices() to pick Heads or Tails 8 times
>>> picks = []
... picks.extend(choices(['H', 'T'], k=8))
... print(picks)
['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']

무작위 정수 만들기

randrange() 함수에는 range(start, stop, step)에서 무작위 값을 고르는 세 가지 형태가 있어요.

  1. randrange(stop)은 0 <= n < stop인 정수 n을 줘요.
  2. randrange(start, stop)은 start <= n < stop인 정수 n을 줘요.
  3. randrange(start, stop, step)은 start <= n < stop이고 n이 start, start + step, start + 2*step... 수열에 속하는 정수 n을 줘요.

step == 1인 흔한 경우에는 randint(a, b) 함수가 더 편리하고 읽기 쉬워요. randint()의 결과에는 상한이 _포함_되므로, randint(a, b)는 randrange(a, b+1)을 사용하는 것과 같아요.

>>> import random

# Select one number at random from the range 0, 499
>>> random.randrange(500)
219

# Select 10 numbers at random between 0 and 9 two steps apart.
>>> numbers = []
... for integer in range(10):
...    numbers.append(random.randrange(0, 10, 2))

>>> print(numbers)
[2, 8, 4, 0, 4, 2, 6, 6, 8, 8]

# roll a die
>>> random.randint(1, 6)  
4

시퀀스 다루기

이번 절의 함수들은 시퀀스나 다른 컨테이너에서 시작한다고 가정해요.

보통은 list이고, 몇 가지 제약은 있지만 tuple이나 set일 수도 있어요 (tuple은 불변이고, set은 순서가 없어요).

choice()와 choices()

choice() 함수는 주어진 시퀀스에서 무작위로 고른 항목 하나를 반환해요. 가장 단순한 예로는 동전 던지기가 있어요.

# This will pick one of the two values in the list at random 5 separate times 
>>> [random.choice(['H', 'T']) for _ in range(5)]
['T', 'H', 'H', 'T', 'H']

choices() 함수를 사용해서 리스트 길이를 키워드 인자로 넘기면 사실상 같은 일을 할 수 있어요.

>>> random.choices(['H', 'T'], k=5)
['T', 'H', 'T', 'H', 'H']

위 예제에서는 앞면과 뒷면의 확률이 같은 공정한 동전을 가정했지만, 가중치를 지정할 수도 있어요. 예를 들어 주머니에 빨간 공이 10개, 초록 공이 15개 들어 있고, 그중 하나를 무작위로 꺼내고 싶다면 이렇게 해요.

>>> random.choices(['red', 'green'], [10, 15])
['red']

sample()

위의 choices() 예제는 통계학자들이 "복원 추출"이라고 부르는 것을 가정해요. 각 선택은 이후 선택의 확률에 영향을 주지 않으며, 선택할 수 있는 값의 분포는 매번 똑같아요.

빨간 공과 초록 공 예제에서, 각 선택 후에는 공을 주머니에 되돌려 넣고 다음 선택 전에 잘 섞어요. 이는 빨간 공을 꺼낸 뒤 그대로 꺼내 둔 채로 두는 상황과 대조돼요. 공을 되돌려 넣지 않으면 주머니에 남은 빨간 공이 줄어들어서, 다음 선택에서 빨간 공이 뽑힐 확률이 더 낮아져요.

이렇게 "비복원 추출"을 시뮬레이션하기 위해 random 모듈은 sample() 함수를 제공해요. sample()의 문법은 choices()와 비슷하지만, counts 키워드 매개변수가 추가돼요.

>>> random.sample(['red', 'green'], counts=[10, 15], k=10)
['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green']

표본은 뽑힌 순서대로 반환돼요.

shuffle()

choices()와 sample()은 k > 1일 때 새로운 리스트를 반환해요. 반대로 shuffle()은 리스트의 순서를 제자리에서 무작위로 바꾸고, 원래 순서는 사라져요.

>>> my_list = [1, 2, 3, 4, 5]
>>> random.shuffle(my_list)
>>> my_list
[4, 1, 5, 2, 3]

분포 다루기

지금까지는 모든 결과가 똑같이 나올 가능성이 있는 경우에 집중했어요. 예를 들어 random.randrange(100)은 0부터 99까지의 어떤 정수든 똑같은 확률로 줘요.

실제 세상의 많은 상황은 이보다 훨씬 복잡해요. 그래서 통계학자들은 "실제 세상"의 결과를 수학적으로 설명하기 위해 다양한 distributions를 만들어 냈어요.

균등 분포

정수의 경우 모든 확률이 같을 때 randrange()와 randint()를 사용해요. 이것을 uniform 분포라고 해요.

randrange()와 randint()에 대응하는 부동소수점 버전도 있어요.

__random()__은 0.0 <= x < 1.0인 float 값 x를 줘요.

__uniform(a, b)__는 a <= x <= b인 x를 줘요.

>>> [round(random.random(), 3) for _ in range(5)]
[0.876, 0.084, 0.483, 0.22, 0.863]

>>> [round(random.uniform(2, 5), 3) for _ in range(5)]
[2.798, 2.539, 3.779, 3.363, 4.33]

가우시안 분포

"정규" 분포 또는 "종 모양" 곡선이라고도 불리는 이것은 측정값의 부정확함을 설명하는 아주 흔한 방법이에요.

예를 들어, 여러분이 일하는 공장에서 똑같아야 할 볼트 10,000개를 막 사 왔다고 해봐요. 이 볼트를 다루도록 공장 로봇을 설정하려고, 100개를 표본으로 무게를 재 보니 평균(또는 mean) 무게가 4.731g이었어요. 이것이 모든 볼트의 무게가 정확히 4.731g이라는 뜻일 가능성은 거의 없어요. 아마 값이 4.627g에서 4.794g까지 분포하지만 4.731g 근처에 몰려 있을 수 있어요.

이것이 Gaussian distribution로, 확률이 평균에서 가장 높고 양쪽으로 대칭을 이루며 줄어들어요 (그래서 "종 모양"이에요). 소프트웨어에서 이것을 시뮬레이션하려면 곡선의 폭을 지정할 방법이 필요해요 (보통 비싼 볼트가 싼 볼트보다 평균 근처에 더 촘촘히 몰려 있어요!).

관례적으로 이것은 standard deviation로 나타내요. 값이 작으면 뾰족하고 좁은 곡선이, 크면 낮고 넓은 곡선이 돼요. 수학자들은 그리스 문자를 좋아해서, 평균을 나타낼 때는 μ('뮤')를, 표준 편차를 나타낼 때는 σ('시그마')를 사용해요. 따라서 "값의 95%가 μ에서 2σ 이내에 있다"거나 "힉스 보손이 5시그마 신뢰도로 검출되었다"는 글을 읽었다면, 이런 설명은 표준 편차와 관련이 있어요.

>>> mu = 4.731
>>> sigma = 0.316
>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)]
[4.72, 4.957, 4.64, 4.556, 4.968]
GitHub에서 편집 링크가 새 창이나 탭에서 열려요