すばらしい回答をありがとうございました。私は共有したい次の解決策になりました。
理由と方法についてさらに詳しく説明する前に、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ソリューションを好みました。
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%)以上の確率があります。したがって、これは非常に効率的です。
私のスクリプトは基本的にFalcoの回答の具体的な実装であり、bashの組み込みビット単位操作を使用して目的の長さのビット文字列をサンプリングするため、純粋なbashで記述され、非常に効率的です。さらに、Eliah Kaganによるアイデアを尊重します。このアイデアは$RANDOM
、の繰り返しの呼び出しから生じるビット文字列を連結することにより、組み込み変数を使用することを提案しています$RANDOM
。私は実際に使用する可能性の両方を実装/dev/urandom
して$RANDOM
。デフォルトでは、上記のスクリプトはを使用し$RANDOM
ます。(そして、使用/dev/urandom
する場合、odとtrが必要ですが、これらはPOSIXによってサポートされています。)
それでは、どのように機能しますか?
私がこれに入る前に、2つの観察:
bashは2 63 -1 より大きい整数を処理できないことがわかりました。自分で見て:
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
bashは内部で符号付き64ビット整数を使用して整数を格納しているようです。したがって、2 63で「ラップアラウンド」し、負の整数を取得します。したがって、使用する任意のランダム関数で2 63 -1を超える範囲を取得することはできません。Bashは単にそれを処理できません。
我々は間の任意の範囲の値にサンプリングしたいとき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ビット左にシフトし、ビット単位または2回目の呼び出しで$RANDOM
、2つの独立してサンプリングされたビット文字列を連結します(または少なくとも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のビット文字列を読み取ります。前の方法と同様に、サンプリングされたビットの等価数が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
必ずしも、2の累乗任意でよいです。(バイアスを作成するため、モジュロは使用できません。)
値を表現するのに必要なだけのビットをサンプリングしようと努力した理由はmax
、ループを安全に(そして効率的に)使用して、n
より低い値をサンプリングするまで-bitビット文字列を繰り返しサンプリングできるようになったからですまたはに等しい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スクリプトに入れましょう。いくつかの引数解析のものを行う...我々は2つの引数たい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
...と、最終的には、間のランダムなA値で均一にサンプリングするmin
とmax
、我々は間のランダムな整数のサンプル0
との絶対値をmax-min
、そして追加min
最終的な結果に。:-)
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
これに触発されて、私はdieharderを使用してこのPRNGのテストとベンチマークを試み、私の発見をここに入れようとします。:-)