답안지

답안지

나는 항상 최선의 방법이 무엇인지 궁금했습니다.좋아요MINBash의 무작위성, 즉 와 MAX사이에서 임의의 양의 정수를 얻는 프로세스는 무엇입니까?

  1. 범위는 임의로 클 수 있습니다(또는 적어도 최대 2 32 -1).
  2. 값은 균등하게 분포됩니다(즉, 편견이 없음).
  3. 효과가있다.

Bash에서 무작위성을 달성하는 효과적인 방법은 $RANDOM변수를 사용하는 것입니다. 그러나 이는 0과 2 15 -1 사이의 값만 샘플링하므로 모든 목적에 충분하지 않을 수 있습니다. 사람들은 일반적으로 모듈로를 사용하여 원하는 범위에 넣습니다.

MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))

$MAX게다가 이는 정확히 2 15 -1 = 32767로 나누어지지 않는 한 편향을 생성합니다. 예를 들어 가 $MIN0이고 9라면 절대 32768이나 32769가 될 수 없기 $MAX때문에 0~7의 값이 8과 9의 값보다 약간 더 가능성이 높습니다 . $RANDOM이 편향은 범위가 증가함에 따라 더욱 심해집니다. 예를 들어 가 $MIN0이고 $MAX9999인 경우 숫자 0에서 2767까지의 확률은 4/32767반면 숫자 2768에서 9999까지의 확률은 3/32767에 불과 합니다 .

따라서 위의 방법은 조건 3을 만족하지만, 조건 1과 2를 만족하지 않는다.

조건 1과 2를 만족시키려고 노력하면서 지금까지 생각해낸 가장 좋은 방법은 /dev/urandom다음을 사용하는 것입니다.

MIN=0
MAX=1234567890
while
  rnd=$(cat /dev/urandom | tr -dc 0-9 | fold -w${#MAX} | head -1 | sed 's/^0*//;')
  [ -z $rnd ] && rnd=0
  (( $rnd < $MIN || $rnd > $MAX ))
do :
done

기본적으로 암호학적으로 강력한 의사 난수 생성기가 필요한 경우 /dev/urandom( /dev/random암호적으로 강력한 의사 난수 생성기가 필요하고위치시간 또는 하드웨어 난수 생성기), 십진수가 아닌 모든 문자를 제거하고 출력을 길이에 맞게 접은 다음 $MAX선행 0을 제거합니다. 우연히 0만 얻으면 비어 있으므로 $rnd이 예에서는 rnd로 설정됩니다 0. 결과가 우리의 범위를 벗어나는지 확인하고 그렇다면 반복하십시오. 루프를 시뮬레이션한다는 정신으로 처음부터 정의되지 않았 do ... while으므로 몸체가 적어도 한 번 실행되도록 while 루프의 "본문"을 가드에 강제로 적용했습니다 .rnd

여기서는 조건 1과 2를 만족했다고 생각했는데, 지금은 조건 3을 망쳤습니다. 이것은 약간 느립니다. 최대 1초 정도 걸립니다(운이 좋으면 10분의 1초 정도). 실제로 루프가 종료된다는 보장도 없습니다(시간이 증가함에 따라 종료 확률이 1로 수렴되지만).

Bash에서는 미리 지정되고 잠재적으로 큰 범위 내에서 편향되지 않은 임의의 정수를 얻는 효율적인 방법이 있습니까? (시간이 허락한다면 계속 조사해 볼 예정이지만, 그러는 동안 여기 누군가가 좋은 아이디어를 갖고 있을 수도 있겠다는 생각이 들었어요!)

답안지

  1. 가장 기본적인(따라서 이식 가능한) 아이디어는 충분히 긴 임의의 비트 문자열을 생성하는 것입니다. 임의의 비트 문자열을 생성하는 방법에는 여러 가지가 있습니다. bash의 내장 변수를 사용 하거나 and (또는 )를 $RANDOM사용할 수 있습니다 . 난수가 더 크면 다시 시작하세요.od/dev/urandom/dev/random$MAX

  2. 또는 외부 도구를 사용할 수도 있습니다.

    • 펄 솔루션
      • 장점: 휴대성이 뛰어나고 단순하며 유연합니다.
      • 대조: 2 32 -1 보다 큰 숫자 에는 적합하지 않습니다.
    • 파이썬 솔루션
      • 장점: 단순하고 유연하며 대용량 데이터에도 적합
      • 단점: 휴대성이 좋지 않음
    • zsh 솔루션
      • 장점: zsh를 사용하는 사람들에게는 여전히 좋습니다.
      • 반대: 아마도 휴대성이 떨어질 것입니다.

답변1

또 다른 흥미로운 접근 방식을 보았습니다.여기.

rand=$(openssl rand 4 | od -DAn)

이것하나도 좋은 선택인 것 같습니다. 임의의 장치에서 4바이트를 읽고 0와 사이의 부호 없는 정수 로 형식을 지정합니다 2^32-1.

rand=$(od -N 4 -t uL -An /dev/urandom | tr -d " ")

답변2

훌륭한 답변을 주신 모든 분들께 감사드립니다. 나는 여러분 모두와 공유하고 싶은 다음과 같은 해결책을 찾았습니다.

그 이유와 방법을 자세히 설명하기 전에 먼저 간단한 소개를 하겠습니다.너무 길어요.:내 빛나는 새 스크립트 :-)

#!/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/randbash에 저장하면, 가능한 경우 주어진 임의의 범위에서 정수를 샘플링하는 멋진 무작위 함수를 갖게 됩니다. 범위는 음수와 양수를 포함할 수 있으며 최대 길이는 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

다른 답변자의 아이디어는 모두 훌륭합니다. 대답은 다음과 같습니다.테든,JF 세바스찬, 그리고조립식 쇠지레외부 도구를 사용하여 간단하고 효과적인 방법으로 작업을 완료하세요. 그러나 나는 최대의 이식성을 위해 진정한 bash 솔루션을 선호합니다. 단지 bash를 좋아하기 때문에 약간일 수도 있습니다.

라메쉬'모래l0b0대답은 와 함께 사용 /dev/urandom하거나 결합하는 것입니다 . 그러나 이 방법의 단점은 이 방법이 바이트, 즉 길이 8의 비트 문자열을 샘플링하기 때문에 0에서 2 8n -1 범위의 일부 n 임의의 정수만 샘플링할 수 있다는 것입니다 . 이는 n을 증가시키기 위한 상당한 점프입니다./dev/randomod

마침내,팔코대답은 이를 수행하는 방법에 대한 일반적인 아이디어를 설명합니다.평상복범위(단지 2의 거듭제곱이 아님). 기본적으로 주어진 범위에 대해 {0..max}2의 다음 거듭제곱이 무엇인지 결정할 수 있습니다.조금max비트 문자열로 표현되어야 합니다 . 그런 다음 많은 비트를 샘플링하여 정수인 이 이중 문자열이 .보다 큰지 확인할 수 있습니다 max. 그렇다면 다시 말씀해 주세요. 표현에 필요한 비트 수를 샘플링하므로 max각 반복의 성공 확률은 50% 이상입니다(최악의 경우 50%, 최상의 경우 100%). 그래서 이것은 매우 효과적입니다.

내 스크립트는 기본적으로 순수 bash로 작성된 Falco 답변의 구체적인 구현이며 bash의 내장 비트 연산을 사용하여 원하는 길이의 비트 문자열을 샘플링하기 때문에 매우 효율적입니다. 그것은 또한 아이디어를 존중합니다엘리아 케이건$RANDOM이는 반복 호출로 생성된 비트 문자열을 연결하여 내장 변수를 사용하는 것을 제안합니다 $RANDOM. 실제로 /dev/urandom및 를 사용하여 가능성을 구현했습니다 $RANDOM. 기본적 으로 위 스크립트는 $RANDOM./dev/urandomOD그리고, 그러나 이는 POSIX에서 지원됩니다. )

그럼 어떻게 작동하나요?

토론을 시작하기 전에 두 가지 관찰을 해보겠습니다.

  1. bash는 2 63 -1보다 큰 정수를 처리할 수 없다는 것이 밝혀졌습니다 . 스스로 봐:

    $ echo $((2**63-1))
    9223372036854775807
    $ echo $((2**63))
    -9223372036854775808
    

    내부적으로 bash는 부호 있는 64비트 정수를 사용하여 정수를 저장하는 것 같습니다. 따라서 2 63 에서 "wraparound"하면 음의 정수를 얻습니다. 따라서 어떤 무작위 함수를 사용하더라도 2 63 -1보다 큰 범위를 얻을 수는 없습니다. Bash는 단순히 그것을 처리할 수 없습니다.

  2. minmax사이의 임의 범위의 가능한 값을 샘플링 하고 싶을 때마다 간단히 와 사이의 값을 샘플링한 다음 최종 결과에 추가하면 min != 0됩니다 . 이것은 여전히 ​​​​작동할 것 입니다 .0max-minminminmax부정적인0, 하지만 사이의 값을 샘플링 하는 데 주의가 필요합니다.절대값 max-min. 그런 다음 0와 양의 정수 사이에서 임의의 값을 샘플링하는 방법에 집중할 수 있습니다 max. 나머지는 쉽습니다.

1단계: 정수(로그)를 표현하는 데 필요한 비트 수 결정

따라서 주어진 값에 대해 max이를 비트 문자열로 표현하는 데 몇 비트가 필요한지 알고 싶습니다. 이런 방식으로 나중에 필요한 비트 수만 무작위로 샘플링할 수 있으므로 스크립트가 매우 효율적이 됩니다.

보자. 비트를 사용하면 n최대 2n -1 의 값을 표현할 수 있으므로 n어떤 값을 표현하는 데 필요한 비트 수는 x상한(log 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

가장 이식성이 뛰어난 아이디어는 bash의 내장 변수를 사용하는 것입니다 /dev/urandom(또는 그렇게 해야 할 충분한 이유가 있는 경우도 있음) . 먼저 이를 수행하는 방법을 살펴보겠습니다 ./dev/random$RANDOM$RANDOM

옵션 A: 사용$RANDOM

이는 다음을 사용합니다.아이디어엘리야 케이건(Elijah Kagan)이 이를 언급했습니다. 기본적으로 $RANDOM우리는 15비트 정수를 샘플링하므로 이를 사용하여 $((RANDOM<<15|RANDOM))30비트 정수를 샘플링할 수 있습니다. 이는 첫 번째 호출을 $RANDOM15비트 왼쪽으로 이동하고 두 번째 호출에 비트별 OR을 적용하여 $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

또는 odsum을 사용하여 /dev/urandomn비트 정수를 샘플링할 수 있습니다. od길이가 8인 비트 문자열인 바이트를 읽습니다. 이전 방법과 유사하게 동일한 수의 샘플과 동일한 바이트 수를 샘플링합니다.조금n보다 크거나 같으며 초과 비트를 자릅니다.

최소한 n 비트를 얻는 데 필요한 최소 바이트 수는 n보다 크거나 같은 8의 가장 작은 배수, 즉 Floor((n+7)/8)입니다.

이는 최대 56비트의 정수에서만 작동합니다. 1바이트를 더 샘플링하면 bash가 처리할 수 없는 최대값인 2 64 -1 인 64비트 정수가 생성됩니다 .

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) ))
}

조각을 하나로 모으기: 임의의 정수 얻기평상복범위

이제 -bit 비트 문자열을 샘플링 할 수 있지만 에서 ~ n까지의 정수를 샘플링하고 싶습니다 .0max균일하게 무작위로, 이는 max임의적일 수 있으며 반드시 2의 거듭제곱일 필요는 없습니다. (편향이 발생할 수 있으므로 모듈로를 사용할 수 없습니다.)

값을 표현하는 데 필요한 비트 수를 너무 세게 샘플링하는 이유 max는 이제 루프를 사용하여 n더 낮은 값이 샘플링될 때까지 -bit 문자열을 반복적으로 샘플링할 수 있기 때문입니다. 또는 max최악의 경우( max2의 거듭제곱)에서는 각 반복이 50% 확률로 종료되는 반면, 가장 좋은 경우( max2의 거듭제곱 - 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. 여기서 합계는 임의적일 수 있고 심지어 음수일 수도 있습니다. 언급했듯이 이것은 이제 사소한 일입니다.minmax

모든 것을 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과 의 절대값 사이의 임의의 정수를 샘플링하여 최종 결과에 추가합니다. :-)0max-minmin

diff=$((max-min)) && diff=${diff#-}

echo $(( $(rand $diff) + min ))

에서 영감을 받다이것, 나는 다음을 사용해 볼 수도 있습니다다이 하드이 PRNG를 테스트하고 벤치마킹하고 여기에 결과를 게시합니다. :-)

답변3

zsh가 될까요?

zmodload zsh/mathfunc
max=1000
integer rnd='rand48() * max'

(0~999 사이의 난수인 경우)

와 함께 씨앗을 사용할 수도 있습니다 rand48(seed). 관심이 있으시면 자세한 설명을 보시고 받아보시기 man zshmodules바랍니다 .man 3 erand48

답변4

번호를 원하시면0통과하다(2^n)-1어디n 모듈로 8 = 0당신은 간단하게 얻을 수 있습니다n/8. /dev/random​예를 들어 난수의 십진수 표현을 얻으려면 다음을 int수행할 수 있습니다.

od --read-bytes=4 --address-radix=n --format=u4 /dev/random | awk '{print $1}'

그냥 갖고싶다면N 조금네가 먼저 가져도 돼천장(n/8)바이트 및오른쪽으로 이동해라원하는 금액으로. 예를 들어 15비트를 원하는 경우:

echo $(($(od --read-bytes=2 --address-radix=n --format=u4 /dev/random | awk '{print $1}') >> 1))

당신이 절대적으로 확신한다면무작위성의 품질에 신경 쓰지 마십시오.그리고 당신은 보장하고 싶습니다최소 실행 시간/dev/urandom대신 사용할 수 있습니다 /dev/random. 사용하기 전에 무엇을 하고 있는지 확인하세요 /dev/urandom!

관련 정보