"random" --- 의사 난수 생성
***************************

**소스 코드:** Lib/random.py

======================================================================

이 모듈은 다양한 분포에 대한 의사 난수 생성기를 구현합니다.

정수에 대해서는, 범위에서 균일한 선택이 있습니다. 시퀀스에 대해서는,
무작위 요소의 균일한 선택, 리스트를 제자리(in-place)에서 임의 순열을
생성하는 함수 및 중복 없는(without replacement) 무작위 표본 추출
(sampling)을 위한 함수가 있습니다.

실수에 대해서는, 균일(uniform), 정규(normal) (가우시안(Gaussian)), 로
그 정규(lognormal), 음의 지수(negative exponential), 감마(gamma) 및 베
타(beta) 분포를 계산하는 함수가 있습니다. 각도 분포를 생성하기 위해,
폰 미제스(von Mises) 분포를 사용할 수 있습니다.

거의 모든 모듈 함수는 기본 함수 "random()"에 의존하는데, 이 함수는 반
열린 구간 "0.0 <= X < 1.0" 에서 무작위 float를 균일하게 생성합니다. 파
이썬은 메르센 트위스터(Mersenne Twister)를 핵심 생성기로 사용합니다.
53비트 정밀도의 float를 생성하며, 주기는 2**19937-1 입니다. C로 작성된
하부 구현은 빠르고 스레드 안전합니다. 메르센 트위스터는 가장 광범위하
게 테스트 된 난수 생성기 중 하나입니다. 그러나, 완전히 결정적이므로,
모든 목적에 적합하지는 않으며, 암호화 목적에는 전혀 적합하지 않습니다.

이 모듈에서 제공하는 함수는 실제로는 "random.Random" 클래스의 숨겨진
인스턴스에 대해 연결된 메서드입니다. "Random" 인스턴스를 직접 인스턴스
화하여 상태를 공유하지 않는 생성기를 얻을 수 있습니다.

Class "Random" can also be subclassed if you want to use a different
basic generator of your own devising: see the documentation on that
class for more details.

"random" 모듈은 운영 체제에서 제공하는 소스에서 난수를 생성하는 시스템
함수 "os.urandom()"을 사용하는 "SystemRandom" 클래스도 제공합니다.

경고:

  이 모듈의 의사 난수 생성기를 보안 목적으로 사용해서는 안 됩니다. 보
  안이나 암호화 용도를 위해서는, "secrets" 모듈을 참조하십시오.

더 보기:

  M. Matsumoto and T. Nishimura, "Mersenne Twister: A
  623-dimensionally equidistributed uniform pseudorandom number
  generator", ACM Transactions on Modeling and Computer Simulation
  Vol. 8, No. 1, January pp.3--30 1998.

  긴 주기와 비교적 간단한 업데이트 연산을 하는 호환 가능한 대체 난수
  생성기를 위한 Complementary-Multiply-with-Carry recipe


관리 함수
=========

random.seed(a=None, version=2)

   난수 생성기를 초기화합니다.

   *a*가 생략되거나 "None"이면, 현재 시스템 시간이 사용됩니다. 운영 체
   제에서 임의성 소스(randomness sources)를 제공하면, 시스템 시간 대신
   사용됩니다 (가용성에 대한 자세한 내용은 "os.urandom()" 함수를 참조
   하십시오).

   *a*가 int이면, 직접 사용됩니다.

   버전(version) 2(기본값)에서는, "str", "bytes" 또는 "bytearray" 객체
   가 "int"로 변환되어 모든 비트가 사용됩니다.

   버전(version) 1(이전 버전의 파이썬에서 온 임의의 시퀀스를 재현하기
   위해 제공됩니다)에서는, "str"과 "bytes"를 위한 알고리즘은 더 좁은
   범위의 시드(seed)를 생성합니다.

   버전 3.2에서 변경: 문자열 시드의 모든 비트를 사용하는 버전 2 체계로
   이동했습니다.

   버전 3.11에서 변경: *seed*는 다음 형 중 하나여야 합니다: "None",
   "int", "float", "str", "bytes" 또는 "bytearray".

random.getstate()

   생성기의 현재 내부 상태를 포착하는 객체를 반환합니다. 이 객체는
   "setstate()"로 전달되어 상태를 복원 할 수 있습니다.

random.setstate(state)

   *state*는 "getstate()"에 대한 이전 호출에서 얻은 것이어야 하고,
   "setstate()"는 생성기의 내부 상태를 "getstate()"가 호출될 당시의 상
   태로 복원합니다.


바이트열 함수
=============

random.randbytes(n)

   *n* 무작위 바이트를 생성합니다.

   이 메서드를 사용하여 보안 토큰을 생성해서는 안 됩니다. 대신
   "secrets.token_bytes()"를 사용하십시오.

   Added in version 3.9.


정수 함수
=========

random.randrange(stop)
random.randrange(start, stop[, step])

   Return a randomly selected element from "range(start, stop, step)".

   This is roughly equivalent to "choice(range(start, stop, step))"
   but supports arbitrarily large ranges and is optimized for common
   cases.

   The positional argument pattern matches the "range()" function.

   Keyword arguments should not be used because they can be
   interpreted in unexpected ways. For example "randrange(start=100)"
   is interpreted as "randrange(0, 100, 1)".

   버전 3.2에서 변경: "randrange()"는 균일하게 분포된 값을 생성하는 데
   있어 더욱 정교합니다. 이전에는 약간 고르지 않은 분포를 생성 할 수
   있는 "int(random()*n)"와 같은 스타일을 사용했습니다.

   버전 3.12에서 변경: Automatic conversion of non-integer types is no
   longer supported. Calls such as "randrange(10.0)" and
   "randrange(Fraction(10, 1))" now raise a "TypeError".

random.randint(a, b)

   "a <= N <= b"를 만족하는 임의의 정수 *N*을 반환합니다.
   "randrange(a, b+1)"의 별칭.

random.getrandbits(k)

   *k* 임의의 비트를 갖는 음이 아닌 파이썬 정수를 반환합니다. 이 메서
   드는 메르센 트위스터(Mersenne Twister) 생성기와 함께 제공되며, 일부
   다른 생성기도 API의 선택적 부분으로 제공할 수 있습니다. 사용 가능할
   때, "getrandbits()"는 "randrange()"가 임의로 넓은 범위를 처리 할 수
   있도록 합니다.

   버전 3.9에서 변경: 이 메서드는 이제 *k*에 0을 허용합니다.


시퀀스 함수
===========

random.choice(seq)

   비어 있지 않은 시퀀스 *seq*에서 임의의 요소를 반환합니다. *seq*가
   비어 있으면, "IndexError"를 발생시킵니다.

random.choices(population, weights=None, *, cum_weights=None, k=1)

   *population*에서 중복을 허락하면서(with replacement) 선택한 *k* 크
   기의 요소 리스트를 반환합니다. *population*이 비어 있으면,
   "IndexError"를 발생시킵니다.

   *weights* 시퀀스가 지정되면, 상대 가중치에 따라 선택됩니다. 대안적
   으로, *cum_weights* 시퀀스가 제공되면, (아마도
   "itertools.accumulate()"를 사용하여 계산된) 누적 가중치(cumulative
   weights)에 따라 선택이 이루어집니다. 예를 들어, 상대 가중치 "[10,
   5, 30, 5]"는 누적 가중치 "[10, 15, 45, 50]"과 동등합니다. 내부적으
   로, 상대 가중치는 선택하기 전에 누적 가중치로 변환되므로, 누적 가중
   치를 제공하면 작업이 줄어듭니다.

   *weights*나 *cum_weights*를 지정하지 않으면, 같은 확률로 선택됩니다
   . weights 시퀀스가 제공되면, *population* 시퀀스와 길이가 같아야 합
   니다. *weights*와 *cum_weights*를 모두 지정하는 것은 "TypeError"입
   니다.

   *weights*나 *cum_weights*는 "random()"이 반환하는 "float" 값과 상호
   운용되는 모든 숫자 형을 사용할 수 있습니다 (정수, 부동 소수점
   (float) 및 유리수(fractions)는 포함하지만, 십진수(decimal)는 제외합
   니다). 가중치는 음수가 아니고 유한하다고 가정합니다. 모든 가중치가
   0이면 "ValueError"가 발생합니다.

   주어진 시드에 대해, 균등한 가중치를 갖는 "choices()" 함수는 일반적
   으로 "choice()"에 대한 반복 호출과는 다른 시퀀스를 생성합니다.
   "choices()"에서 사용하는 알고리즘은 내부 일관성과 속도를 위해 부동
   소수점 산술을 사용합니다. "choice()"에서 사용하는 알고리즘은 자리
   올림 오차로 인한 작은 바이어스(bias)를 피하려고 반복 선택을 통한 정
   수 산술로 기본 설정됩니다.

   Added in version 3.6.

   버전 3.9에서 변경: 모든 가중치가 0이면 "ValueError"를 발생시킵니다.

random.shuffle(x)

   시퀀스 *x*를 제자리에서 섞습니다.

   불변 시퀀스를 섞고 새로운 섞인 리스트를 반환하려면, 대신 "sample(x,
   k=len(x))"를 사용하십시오.

   작은 "len(x)"의 경우에조차, *x*의 총 순열 수는 대부분의 난수 생성기
   주기보다 빠르게 커질 수 있습니다. 이것은 긴 시퀀스의 대부분 순열이
   절대로 생성될 수 없음을 의미합니다. 예를 들어, 길이가 2080인 시퀀스
   가 메르센 트위스터 난수 생성기의 주기 안에 들어갈 수 있는 최대입니
   다.

   버전 3.11에서 변경: 선택적 매개 변수 *random*을 제거했습니다.

random.sample(population, k, *, counts=None)

   population 시퀀스에서 선택한 고유한 요소의 *k* 길이 리스트를 반환합
   니다. 중복 없는(without replacement) 무작위 표본 추출(sampling)에
   사용됩니다.

   원래 population을 변경하지 않고, population의 요소를 포함하는 새 리
   스트를 반환합니다. 결과 리스트는 선택 순서를 따라서, 모든 서브 슬라
   이스도 유효한 임의의 표본이 됩니다. 이것은 추첨 당첨자(표본)를 대상
   (grand prize)과 차점자들(서브 슬라이스)로 나눌 수 있도록 합니다.

   population의 멤버는 *해시 가능*하거나 고유할 필요가 없습니다.
   population이 반복을 포함하면, 각 등장(occurrence)은 표본에서 가능한
   선택입니다.

   반복되는 요소는 한 번에 하나씩 또는 선택적 키워드 전용 *counts* 매
   개 변수로 지정할 수 있습니다. 예를 들어 "sample(['red', 'blue'],
   counts=[4, 2], k=5)"는 "sample(['red', 'red', 'red', 'red', 'blue',
   'blue'], k=5)"와 동등합니다.

   정수 범위에서 표본을 선택하려면, "range()" 객체를 인자로 사용하십시
   오. 이는 큰 population에서 표본 추출할 때 특히 빠르고 공간 효율적입
   니다: "sample(range(10000000), k=60)".

   표본 크기가 population 크기보다 크면 "ValueError"가 발생합니다.

   버전 3.9에서 변경: *counts* 매개 변수를 추가했습니다.

   버전 3.11에서 변경: The *population* must be a sequence.  Automatic
   conversion of sets to lists is no longer supported.


이산 분포
=========

The following function generates a discrete distribution.

random.binomialvariate(n=1, p=0.5)

   Binomial distribution. Return the number of successes for *n*
   independent trials with the probability of success in each trial
   being *p*:

   Mathematically equivalent to:

      sum(random() < p for i in range(n))

   The number of trials *n* should be a non-negative integer. The
   probability of success *p* should be between "0.0 <= p <= 1.0". The
   result is an integer in the range "0 <= X <= n".

   Added in version 3.12.


실수 분포
=========

다음 함수는 특정 실수 분포를 생성합니다. 함수 매개 변수는 일반적인 수
학적 관행에 사용되는 분포 방정식에서 해당 변수의 이름을 따서 명명됩니
다; 이러한 방정식의 대부분은 모든 통계 교과서에서 찾을 수 있습니다.

random.random()

   "0.0 <= X < 1.0" 구간에서 다음 임의의 부동 소수점 숫자를 반환합니다

random.uniform(a, b)

   "a <= b" 일 때 "a <= N <= b", "b < a" 일 때 "b <= N <= a"를 만족하
   는 임의의 부동 소수점 숫자 *N*을 반환합니다.

   종단 값 "b"는 표현식 "a + (b-a) * random()"의 부동 소수점 자리 올림
   에 따라 범위에 포함되거나 포함되지 않을 수 있습니다.

random.triangular(low, high, mode)

   "low <= N <= high"를 만족하고 이 경계 사이에 지정된 *모드(mode)*를
   갖는 임의의 부동 소수점 숫자 *N*을 반환합니다. *low* 및 *high* 경계
   는 기본적으로 0과 1입니다. *mode* 인자는 기본적으로 경계 사이의 중
   간 점으로, 대칭 분포를 제공합니다.

random.betavariate(alpha, beta)

   베타 분포. 매개 변수의 조건은 "alpha > 0"과 "beta > 0"입니다. 반환
   된 값의 범위는 0에서 1입니다.

random.expovariate(lambd=1.0)

   지수 분포. *lambd*는 1.0을 원하는 평균으로 나눈 값입니다. 0이 아니
   어야 합니다. (매개 변수는 "lambda"라고 부르지만, 파이썬에서는 예약
   어입니다.) 반환된 값의 범위는, *lambd*가 양수이면 0에서 양의 무한대
   이고, *lambd*가 음수이면 음의 무한대에서 0입니다.

   버전 3.12에서 변경: Added the default value for "lambd".

random.gammavariate(alpha, beta)

   Gamma distribution.  (*Not* the gamma function!)  The shape and
   scale parameters, *alpha* and *beta*, must have positive values.
   (Calling conventions vary and some sources define 'beta' as the
   inverse of the scale).

   확률 분포 함수는 다음과 같습니다:

      x ** (alpha - 1) * math.exp(-x / beta)
      pdf(x) =  --------------------------------------
                  math.gamma(alpha) * beta ** alpha

random.gauss(mu=0.0, sigma=1.0)

   정규 분포, 가우시안 분포라고도 합니다. *mu*는 평균이고, *sigma*는
   표준 편차입니다. 이것은 아래에 정의된 "normalvariate()" 함수보다 약
   간 빠릅니다.

   다중 스레딩 참고: 두 스레드가 이 함수를 동시에 호출하면, 같은 반환
   값을 받을 수 있습니다. 이것은 세 가지 방법으로 피할 수 있습니다. 1)
   각 스레드가 난수 생성기의 다른 인스턴스를 사용하도록 합니다. 2) 모
   든 호출에 록을 둡니다. 3) 더 느리지만, 스레드 안전한
   "normalvariate()" 함수를 대신 사용합니다.

   버전 3.11에서 변경: *mu* and *sigma* now have default arguments.

random.lognormvariate(mu, sigma)

   로그 정규 분포. 이 분포의 자연로그를 취하면, 평균 *mu*와 표준 편차
   *sigma*를 갖는 정규 분포를 얻게 됩니다. *mu*는 아무 값이나 될 수 있
   으며, *sigma*는 0보다 커야 합니다.

random.normalvariate(mu=0.0, sigma=1.0)

   정규 분포. *mu*는 평균이고, *sigma*는 표준 편차입니다.

   버전 3.11에서 변경: *mu* and *sigma* now have default arguments.

random.vonmisesvariate(mu, kappa)

   *mu*는 0과 2**pi* 사이의 라디안으로 표현된 평균 각도이며, *kappa*는
   0 이상이어야 하는 집중도(concentration) 매개 변수입니다.  *kappa*가
   0이면, 이 분포는 0에서 2**pi*에 걸친 균등한 임의의 각도로 환원됩니
   다.

random.paretovariate(alpha)

   파레토 분포. *alpha*는 모양(shape) 매개 변수입니다.

random.weibullvariate(alpha, beta)

   베이불 분포. *alpha*는 크기(scale) 매개 변수이고 *beta*는 모양
   (shape) 매개 변수입니다.


대체 생성기
===========

class random.Random([seed])

   "random" 모듈에서 사용하는 기본 의사 난수 생성기를 구현하는 클래스.

   버전 3.11에서 변경: 이전에는 *seed*가 해시 가능한 어떤 객체라도 될
   수 있었습니다. 이제는 다음 형으로 제한됩니다: "None", "int",
   "float", "str", "bytes" 또는 "bytearray".

   Subclasses of "Random" should override the following methods if
   they wish to make use of a different basic generator:

   seed(a=None, version=2)

      Override this method in subclasses to customise the "seed()"
      behaviour of "Random" instances.

   getstate()

      Override this method in subclasses to customise the "getstate()"
      behaviour of "Random" instances.

   setstate(state)

      Override this method in subclasses to customise the "setstate()"
      behaviour of "Random" instances.

   random()

      Override this method in subclasses to customise the "random()"
      behaviour of "Random" instances.

   Optionally, a custom generator subclass can also supply the
   following method:

   getrandbits(k)

      Override this method in subclasses to customise the
      "getrandbits()" behaviour of "Random" instances.

class random.SystemRandom([seed])

   운영 체제에서 제공하는 소스에서 난수를 생성하기 위해 "os.urandom()"
   함수를 사용하는 클래스. 모든 시스템에서 사용 가능한 것은 아닙니다.
   소프트웨어 상태에 의존하지 않으며, 시퀀스는 재현되지 않습니다. 따라
   서, "seed()" 메서드는 효과가 없으며, 무시됩니다. "getstate()"와
   "setstate()" 메서드는 호출되면 "NotImplementedError"를 발생시킵니다
   .


재현성에 대한 참고 사항
=======================

때때로 의사 난수 생성기가 만든 시퀀스를 재현하는 것이 유용 할 수 있습
니다. 시드 값을 재사용하면, 여러 스레드가 실행되고 있지 않은 한 실행할
때마다 같은 시퀀스를 재현할 수 있어야 합니다.

random 모듈의 알고리즘과 시딩(seeding) 함수의 대부분은 파이썬 버전에
따라 변경될 수 있지만, 두 가지 측면은 변경되지 않음이 보장됩니다:

* 새로운 시딩 메서드가 추가되면, 이전 버전과 호환되는 시더(seeder)가
  제공될 것입니다.

* 호환 시더에 같은 시드가 제공되면 생성기의 "random()" 메서드는 같은
  시퀀스를 계속 생성할 것입니다.


예제
====

기본 예제:

   >>> random()                          # 무작위 float:  0.0 <= x < 1.0
   0.37444887175646646

   >>> uniform(2.5, 10.0)                # 무작위 float:  2.5 <= x <= 10.0
   3.1800146073117523

   >>> expovariate(1 / 5)                # 평균 5초의 도착 간격
   5.148957571865031

   >>> randrange(10)                     # 경계를 포함하는 0에서 9 사이의 정수
   7

   >>> randrange(0, 101, 2)              # 경계를 포함하는 0에서 100 사이의 짝수 정수
   26

   >>> choice(['win', 'lose', 'draw'])   # 시퀀스로부터의 단일 무작위 요소
   'draw'

   >>> deck = 'ace two three four'.split()
   >>> shuffle(deck)                     # 리스트를 섞습니다
   >>> deck
   ['four', 'two', 'ace', 'three']

   >>> sample([10, 20, 30, 40, 50], k=4) # 중복 없는 4개의 표본
   [40, 10, 50, 30]

시뮬레이션:

   >>> # 여섯 개의 룰렛 휠이 회전합니다 (중복을 허락하는 가중치 있는 샘플링)
   >>> choices(['red', 'black', 'green'], [18, 18, 2], k=6)
   ['red', 'green', 'black', 'black', 'red', 'black']

   >>> # 52장의 카드 덱에서 중복 없이 20장의 카드를 나누고,
   >>> # 10 값(10, 잭, 퀸 또는 킹)을
   >>> # 가진 카드의 비율을 결정합니다.
   >>> deal = sample(['tens', 'low cards'], counts=[16, 36], k=20)
   >>> deal.count('tens') / 20
   0.15

   >>> # 60%의 확률로 앞(head)이 나오는 편향된(biased) 동전을 7번 던졌을 때 5번 이상
   >>> # 앞이 나올 확률을 추정합니다.
   >>> sum(binomialvariate(n=7, p=0.6) >= 5 for i in range(10_000)) / 10_000
   0.4169

   >>> # 다섯 표본의 중간값이 가운데 2개의 사분위(quartile)에 있을 확률
   >>> def trial():
   ...     return 2_500 <= sorted(choices(range(10_000), k=5))[2] < 7_500
   ...
   >>> sum(trial() for i in range(10_000)) / 10_000
   0.7958

표본의 평균에 대한 신뢰 구간을 추정하기 위해 중복을 허용하는(with
replacement) 재표본추출(resampling)을 사용하는 통계적 부트스트래핑
(statistical bootstrapping)의 예:

   # https://www.thoughtco.com/example-of-bootstrapping-3126155
   from statistics import fmean as mean
   from random import choices

   data = [41, 50, 29, 37, 81, 30, 73, 63, 20, 35, 68, 22, 60, 31, 95]
   means = sorted(mean(choices(data, k=len(data))) for i in range(100))
   print(f'The sample mean of {mean(data):.1f} has a 90% confidence '
         f'interval from {means[5]:.1f} to {means[94]:.1f}')

약물과 위약의 효과 간에 관찰된 차이의 통계적 유의성 또는 p-값을 결정하
기 위한 재표본추출 순열 검증(resampling permutation test)의 예:

   # Dennis Shasha 와 Manda Wilson의 "Statistics is Easy" 에서 따온 예제
   from statistics import fmean as mean
   from random import shuffle

   drug = [54, 73, 53, 70, 73, 68, 52, 65, 65]
   placebo = [54, 51, 58, 44, 55, 52, 42, 47, 58, 46]
   observed_diff = mean(drug) - mean(placebo)

   n = 10_000
   count = 0
   combined = drug + placebo
   for i in range(n):
       shuffle(combined)
       new_diff = mean(combined[:len(drug)]) - mean(combined[len(drug):])
       count += (new_diff >= observed_diff)

   print(f'{n} label reshufflings produced only {count} instances with a difference')
   print(f'at least as extreme as the observed difference of {observed_diff:.1f}.')
   print(f'The one-sided p-value of {count / n:.4f} leads us to reject the null')
   print(f'hypothesis that there is no difference between the drug and the placebo.')

다중 서버 큐를 위한 도착 시간과 서비스 제공의 시뮬레이션:

   from heapq import heapify, heapreplace
   from random import expovariate, gauss
   from statistics import mean, quantiles

   average_arrival_interval = 5.6
   average_service_time = 15.0
   stdev_service_time = 3.5
   num_servers = 3

   waits = []
   arrival_time = 0.0
   servers = [0.0] * num_servers  # 각 서버를 사용할 수 있는 시간
   heapify(servers)
   for i in range(1_000_000):
       arrival_time += expovariate(1.0 / average_arrival_interval)
       next_server_available = servers[0]
       wait = max(0.0, next_server_available - arrival_time)
       waits.append(wait)
       service_duration = max(0.0, gauss(average_service_time, stdev_service_time))
       service_completed = arrival_time + wait + service_duration
       heapreplace(servers, service_completed)

   print(f'Mean wait: {mean(waits):.1f}   Max wait: {max(waits):.1f}')
   print('Quartiles:', [round(q, 1) for q in quantiles(waits)])

더 보기:

  시뮬레이션(simulation), 표본 추출(sampling), 섞기(shuffling) 및 교차
  검증(cross-validation)을 포함하는 몇 가지 기본 개념만을 사용한, 통계
  분석에 대한 Jake Vanderplas의 비디오 자습서 Statistics for Hackers

  Economics Simulation 이 모듈에서 제공하는 많은 도구와 분포(gauss,
  uniform, sample, betavariate, choice, triangular 및 randrange)의 효
  과적인 사용을 보여주는 Peter Norvig의 시장(marketplace) 시뮬레이션.

  A Concrete Introduction to Probability (using Python) 확률 이론의 기
  초, 시뮬레이션 작성 방법 및 파이썬을 사용해서 데이터 분석을 수행하는
  방법을 다루는 Peter Norvig의 자습서.


조리법
======

These recipes show how to efficiently make random selections from the
combinatoric iterators in the "itertools" module:

   def random_product(*args, repeat=1):
       "itertools.product(*args, **kwds) 에서 무작위 선택"
       pools = [tuple(pool) for pool in args] * repeat
       return tuple(map(random.choice, pools))

   def random_permutation(iterable, r=None):
       "itertools.permutations(iterable, r) 에서 무작위 선택"
       pool = tuple(iterable)
       r = len(pool) if r is None else r
       return tuple(random.sample(pool, r))

   def random_combination(iterable, r):
       "itertools.combinations(iterable, r) 에서 무작위 선택"
       pool = tuple(iterable)
       n = len(pool)
       indices = sorted(random.sample(range(n), r))
       return tuple(pool[i] for i in indices)

   def random_combination_with_replacement(iterable, r):
       "중복을 허락하면서 r 요소를 선택합니다. 결과를 iterable 과 일치하도록 정렬합니다."
       # 결과는 set(itertools.combinations_with_replacement(iterable, r)) 의 원소입니다.
       pool = tuple(iterable)
       n = len(pool)
       indices = sorted(random.choices(range(n), k=r))
       return tuple(pool[i] for i in indices)

기본 "random()"은 *0.0 ≤ x < 1.0* 범위에서 2⁻⁵³의 배수를 반환합니다.
이러한 모든 숫자는 균등 간격으로 분포되어있고 파이썬 float로 정확하게
표현할 수 있습니다. 그러나, 해당 범위의 다른 많은 표현 가능한 부동 소
수점은 가능한 선택이 아닙니다. 예를 들어, "0.05954861408025609"는 2⁻⁵³
의 정수배가 아닙니다.

다음 조리법은 다른 접근 방식을 사용합니다. 범위의 모든 부동 소수점이
가능한 선택입니다. 가수(mantissa)는 *2⁵² ≤ mantissa < 2⁵³* 범위에 있는
정수의 균등 분포(uniform distribution)에서 옵니다. 지수(exponent)는
*-53*보다 작은 지수가 다음으로 큰 지수의 절반만큼 자주 발생하는 기하
분포(geometric distribution)에서 옵니다.

   from random import Random
   from math import ldexp

   class FullRandom(Random):

       def random(self):
           mantissa = 0x10_0000_0000_0000 | self.getrandbits(52)
           exponent = -53
           x = 0
           while not x:
               x = self.getrandbits(32)
               exponent += x.bit_length() - 32
           return ldexp(mantissa, exponent)

클래스의 모든 실숫값 분포는 새 메서드를 사용합니다:

   >>> fr = FullRandom()
   >>> fr.random()
   0.05954861408025609
   >>> fr.expovariate(0.25)
   8.87925541791544

조리법은 개념적으로 *0.0 ≤ x < 1.0* 범위의 2⁻¹⁰⁷⁴의 모든 배수에서 선택
하는 알고리즘과 동등합니다. 이러한 모든 숫자는 균등한 간격이지만, 대부
분은 가장 가까운 표현 가능한 파이썬 부동 소수점으로 자리 내림해야 합니
다. (값 2⁻¹⁰⁷⁴은 가장 작은 양의 정규화되지 않은 부동 소수점이며
"math.ulp(0.0)"과 같습니다.)

더 보기:

  Generating Pseudo-random Floating-Point Values Allen B. Downey의 논
  문은 "random()"이 일반적으로 생성하는 것보다 더 세밀한 부동 소수점을
  생성하는 방법을 설명합니다.
