모든 훌륭한 답변에 감사드립니다. 나는 다음과 같은 해결책으로 끝내고 싶다.
이유와 방법에 대해 더 자세히 설명하기 전에 여기에 tl; dr : 반짝이는 새 스크립트가 있습니다 :-)
#!/usr/bin/env bash
#
# Generates a random integer in a given range
# computes the ceiling of log2
# i.e., for parameter x returns the lowest integer l such that 2**l >= x
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
# uses $RANDOM to generate an n-bit random bitstring uniformly at random
# (if we assume $RANDOM is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 60 bits
get_n_rand_bits() {
local n=$1 rnd=$RANDOM rnd_bitlen=15
while (( rnd_bitlen < n ))
do
rnd=$(( rnd<<15|$RANDOM ))
let rnd_bitlen+=15
done
echo $(( rnd>>(rnd_bitlen-n) ))
}
# alternative implementation of get_n_rand_bits:
# uses /dev/urandom to generate an n-bit random bitstring uniformly at random
# (if we assume /dev/urandom is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 56 bits
get_n_rand_bits_alt() {
local n=$1
local nb_bytes=$(( (n+7)/8 ))
local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
echo $(( rnd>>(nb_bytes*8-n) ))
}
# for parameter max, generates an integer in the range {0..max} uniformly at random
# max can be an arbitrary integer, needs not be a power of 2
rand() {
local rnd max=$1
# get number of bits needed to represent $max
local bitlen=$(log2 $((max+1)))
while
# could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
rnd=$(get_n_rand_bits $bitlen)
(( rnd > max ))
do :
done
echo $rnd
}
# MAIN SCRIPT
# check number of parameters
if (( $# != 1 && $# != 2 ))
then
cat <<EOF 1>&2
Usage: $(basename $0) [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
EOF
exit 1
fi
# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
min=$max
max=$1
shift
done
# ensure that min <= max
if (( min > max ))
then
echo "$(basename $0): error: min is greater than max" 1>&2
exit 1
fi
# need absolute value of diff since min (and also max) may be negative
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
그것을 저장 ~/bin/rand
하면 주어진 임의의 범위에서 정수를 샘플링 할 수있는 bash의 달콤한 임의 함수를 사용할 수 있습니다. 범위는 음의 정수와 양의 정수를 포함 할 수 있으며 최대 길이는 2 60 -1입니다.
$ rand
Usage: rand [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
$ rand 1 10
9
$ rand -43543 -124
-15757
$ rand -3 3
1
$ for i in {0..9}; do rand $((2**60-1)); done
777148045699177620
456074454250332606
95080022501817128
993412753202315192
527158971491831964
336543936737015986
1034537273675883580
127413814010621078
758532158881427336
924637728863691573
다른 응답자의 모든 아이디어는 훌륭했습니다. terdon , JF Sebastian 및 jimmij 의 답변은 외부 도구를 사용하여 간단하고 효율적인 방식으로 작업을 수행했습니다. 그러나, 나는 최대한의 이식성을 위해 진정한 bash 솔루션을 선호했으며, 아마도 bash에 대한 사랑에서 조금 벗어나는 것이 좋았습니다.)
Ramesh 와 l0b0 의 답변이 사용 /dev/urandom
되거나 /dev/random
함께 사용 됩니다 od
. 그러나이 방법은 바이트 수, 즉 길이 8의 비트 열을 샘플링하기 때문에 일부 n에 대해 0에서 2 8n -1 범위의 난수 만 샘플링 할 수 있다는 단점이있었습니다. n 증가
마지막으로 Falco 의 답변은 임의의 범위 (2의 거듭 제곱이 아닌)에 대해 어떻게 수행 할 수 있는지에 대한 일반적인 아이디어를 설명합니다 . 기본적으로 주어진 범위 {0..max}
에 대해 다음 2의 거듭 제곱이 무엇인지, 즉 비트 열로 표현하는 데 필요한 비트 수를 정확하게 결정할 수 있습니다 max
. 그런 다음이 비트 수를 샘플링하여이 바이 스트링이 정수보다 큰지 확인할 수 max
있습니다. 그렇다면 반복하십시오. 를 나타내는 데 필요한만큼의 비트를 샘플링하기 때문에 max
각 반복의 성공 확률은 성공률의 50 %보다 크거나 같습니다 (최악의 경우 50 %, 가장 좋은 경우 100 %). 따라서 이것은 매우 효율적입니다.
필자의 스크립트는 기본적으로 순수 bash로 작성되고 bash의 내장 비트 연산을 사용하여 원하는 길이의 비트 문자열을 샘플링하기 때문에 Falco의 답변을 구체적으로 구현합니다. Eliah Kagan 의 아이디어는 또한 $RANDOM
반복적으로 호출하여 발생하는 비트 스트링을 연결 하여 내장 변수 를 사용하도록 제안합니다 $RANDOM
. 나는 실제로 사용할 수있는 가능성을 모두 구현 /dev/urandom
및 $RANDOM
. 기본적으로 위 스크립트는을 사용합니다 $RANDOM
. (그리고 괜찮다면 od 와 tr이/dev/urandom
필요 하지만 POSIX가 지원합니다.)
어떻게 작동합니까?
이것에 들어가기 전에 두 가지 관찰이 있습니다.
bash는 2 63 -1 보다 큰 정수를 처리 할 수 없습니다 . 직접 참조하십시오 :
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
bash는 내부적으로 부호있는 64 비트 정수를 사용하여 정수를 저장하는 것으로 보입니다. 따라서 2 63 에서 "포장"하고 음의 정수를 얻습니다. 따라서 우리는 우리가 사용하는 임의의 함수로 2 63 -1 보다 큰 범위를 갖기를 희망하지 않습니다 . 배쉬는 단순히 그것을 처리 할 수 없습니다.
우리 사이의 임의의 범위의 값 샘플링 할 때마다 min
와 max
가능성과를 min != 0
, 우리는 단순히 사이의 값을 샘플링 할 수 0
및 max-min
대신하고 추가 min
최종 결과에. 이 경우에도 작동 min
및 가능성도 max
있다 음 ,하지만 우리 사이의 값 샘플링주의해야 0
와 의 절대 값을 max-min
. 따라서 0
임의의 양의 정수 사이의 임의의 값을 샘플링하는 방법에 중점을 둘 수 있습니다 max
. 나머지는 쉽습니다.
1 단계 : 정수 (로그)를 나타내는 데 필요한 비트 수 결정
따라서 주어진 값 max
에 대해 비트 문자열로 표현하는 데 필요한 비트 수를 알고 싶습니다. 따라서 나중에 필요한만큼만 비트를 무작위로 샘플링 할 수 있으므로 스크립트의 효율성이 높아집니다.
보자 이후와 n
비트, 우리는 2 값까지 표현할 수 N -1, 다음 수가 n
임의의 값을 표현하는데 필요한 비트가 x
천장 인 (로그 2 (X 1 +)). 따라서 밑의 2에 대한 로그의 상한을 계산하는 함수가 필요합니다.
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
우리는 조건 n>0
이 너무 커져서 너무 커지고 감싸지고 부정적이되면 루프가 종료되도록 보장됩니다.
2 단계 : 임의의 길이의 비트 열 샘플링 n
가장 휴대용 아이디어는 /dev/urandom
(또는 /dev/random
강력한 이유가 있더라도 ) 또는 bash의 내장 $RANDOM
변수를 사용하는 것입니다. $RANDOM
먼저 어떻게해야하는지 보자 .
옵션 A : 사용 $RANDOM
이것은 Eliah Kagan이 언급 한 아이디어를 사용합니다 . 기본적으로 $RANDOM
15 비트 정수 $((RANDOM<<15|RANDOM))
를 샘플링하기 때문에 30 비트 정수를 샘플링하는 데 사용할 수 있습니다 . 즉, 첫 번째 호출을 $RANDOM
15 비트 왼쪽으로 이동하고 비트 단위 또는 두 번째 호출을 적용하여 $RANDOM
두 개의 독립적으로 샘플링 된 비트 열을 효과적으로 연결합니다 (또는 적어도 bash의 내장 함수만큼 독립적 $RANDOM
임).
45 비트 또는 60 비트 정수를 얻기 위해 이것을 반복 할 수 있습니다. bash는 더 이상 처리 할 수 없지만 0과 2 60 -1 사이의 임의의 값을 쉽게 샘플링 할 수 있습니다 . 따라서 n 비트 정수를 샘플링하기 위해 길이가 15 비트 단위로 증가하는 임의의 비트 열이 n보다 크거나 같은 길이가 될 때까지 절차를 반복합니다. 마지막으로, 오른쪽으로 비트 단위로 적절히 시프트하여 너무 많은 비트를 잘라 내고, n 비트 랜덤 정수로 끝납니다.
get_n_rand_bits() {
local n=$1 rnd=$RANDOM rnd_bitlen=15
while (( rnd_bitlen < n ))
do
rnd=$(( rnd<<15|$RANDOM ))
let rnd_bitlen+=15
done
echo $(( rnd>>(rnd_bitlen-n) ))
}
옵션 B : 사용 /dev/urandom
또한, 우리가 사용할 수 있습니다 od
및 /dev/urandom
n 비트의 정수를 맛볼 수 있습니다. od
바이트 8, 즉 길이 8의 비트 열을 읽습니다. 이전 방법과 마찬가지로, 우리는 동일한 바이트 수만큼 샘플링 된 비트 수가 n보다 크거나 같고 너무 많은 비트를 잘라냅니다.
최소 n 비트를 얻는 데 필요한 최소 바이트 수는 n보다 크거나 같은 8의 최소 배수입니다 (즉, floor ((n + 7) / 8)).
이것은 최대 56 비트 정수로만 작동합니다. 1 바이트를 더 샘플링하면 64 비트 정수, 즉 bash가 처리 할 수없는 최대 2 64 -1 값을 얻을 수 있습니다.
get_n_rand_bits_alt() {
local n=$1
local nb_bytes=$(( (n+7)/8 ))
local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
echo $(( rnd>>(nb_bytes*8-n) ))
}
조각 정리하기 : 임의의 범위 에서 임의의 정수 가져 오기
우리는 샘플링 할 수 n
해주기 비트 스트링을 비트,하지만 우리의 범위에서 샘플 정수로 원하는 0
에 max
, 균일 무작위로 , 어디에서 max
, 임의적 일 수있는 두 가지의 반드시 전원을. (우리는 모듈로를 사용할 수 없기 때문에 바이어스를 만듭니다.)
우리가 값을 표현하는 데 필요한만큼의 비트를 샘플링하기 위해 열심히 노력한 이유는 max
, n
더 낮은 값을 샘플링 할 때까지 루프를 반복적으로 안전하게 사용하여 루프를 안전하게 샘플링 할 수 있다는 것입니다. 또는 같습니다 max
. 최악의 경우 ( max
2의 거듭 제곱) 각 반복은 50 %의 확률로 종료되고 최상의 경우 ( max
2에서 1의 거듭 제곱 인) 첫 번째 반복은 확실하게 종료됩니다.
rand() {
local rnd max=$1
# get number of bits needed to represent $max
local bitlen=$(log2 $((max+1)))
while
# could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
rnd=$(get_n_rand_bits $bitlen)
(( rnd > max ))
do :
done
echo $rnd
}
물건 정리
마지막으로, 우리는 사이 샘플 정수로 원하는 min
과 max
, 어디에 min
및 max
임의의 수 있습니다, 심지어 부정적인. 앞에서 언급했듯이 이것은 이제 사소한 것입니다.
bash 스크립트에 모두 넣겠습니다. 일부 인수 구문 분석 물건을 할 ... 우리는 두 개의 인수하고자 min
하고 max
, 또는 하나 개의 인수 max
, min
기본값을 0
.
# check number of parameters
if (( $# != 1 && $# != 2 ))
then
cat <<EOF 1>&2
Usage: $(basename $0) [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
EOF
exit 1
fi
# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
min=$max
max=$1
shift
done
# ensure that min <= max
if (( min > max ))
then
echo "$(basename $0): error: min is greater than max" 1>&2
exit 1
fi
... 그리고, 마지막으로, 사이의 임의의 값 균일하게 샘플 min
과 max
우리 사이의 임의의 정수 샘플 0
과의 절대 값 max-min
, 및 추가 min
의 최종 결과. :-)
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
영감을받은 이 , 내가 사용하려고 할 수 dieharder 테스트 및 벤치마킹이 PRNG로, 여기에 내 결과를했습니다. :-)