回答紙

回答紙

私はいつも最善の方法が何であるか疑問に思いました。いいね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な場合場所時間またはハードウェア乱数ジェネレータ)、10進数以外のすべての文字を削除し、出力を長さに合わせて折りたたみ、先行$MAXゼロを削除します。偶然 0 だけ取得すると空になるので、$rndこの例ではrndに設定されます0。結果が私たちの範囲外であることを確認し、そうであれば繰り返します。ループをシミュレートするという精神では最初から定義されていないdo ... whileので、ボディが少なくとも1回実行されるように、whileループの「ボディ」をガードに強制的に適用しました。rnd

ここでは条件1と2を満たしていたと思いましたが、今は条件3を台無しにしました。これは少し遅いです。最大1秒程度かかります(運が良ければ10分の1秒程度)。実際にループが終了するという保証もありません(時間が増えるにつれて終了確率は1に収束しますが)。

Bashでは、事前に指定され、潜在的に大きな範囲内で偏向されていない任意の整数を取得する効率的な方法はありますか? (時間が許せば調査を続ける予定ですが、その間、ここで誰かが良いアイデアを持っているかもしれないと思いました!)

回答紙

  1. 最も基本的な(したがって移植可能な)アイデアは、十分に長いランダムなビット文字列を生成することです。任意のビット文字列を生成する方法はいくつかあります。 bashの組み込み変数を使用するか、and(または)を$RANDOM使用できます。乱数が大きい場合は再起動してください。od/dev/urandom/dev/random$MAX

  2. あるいは、外部ツールを使用することもできます。

    • Perlソリューション
      • 利点:携帯性に優れ、シンプルで柔軟です。
      • 対照:2 32 -1より大きい数字には適していません。
    • Pythonソリューション
      • 利点:シンプルで柔軟性があり、大容量データにも適しています
      • 欠点:携帯性が悪い
    • 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でサポートされています。 )

それでは、どのように機能しますか?

議論を始める前に、2つの観察をしてみましょう。

  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)がこれに言及しました。デフォルトでは、$RANDOM15ビット整数をサンプリングするため、これを使用して$((RANDOM<<15|RANDOM))30ビット整数をサンプリングできます。これは、最初の呼び出しを$RANDOM15ビット左に移動し、2番目の呼び出しにビットごとのORを適用して、2つの独立してサンプリングされた2つの$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、sumを使用して/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) ))
}

フラグメントを1つに集める:任意の整数を取得する普段着範囲

これで-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スクリプトに入れてみましょう。いくつかのパラメータ解析を実行しています... 2つのパラメータが必要です。minまたはmax1つのパラメータのみが必要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 zshmodulesman 3 erand48

答え4

番号が必要な場合0渡す(2^n)-1どこnモジュロ8 = 0あなたは簡単に得ることができますn/8/dev/randomたとえば、乱数の10進数表現を取得するには、次のように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

関連情報