web-dev-qa-db-ja.com

大規模で均一に分散されたランダム整数をbashで効率的に生成する方法は?

私はbashでgoodランダム性を取得するための最良の方法は何であるか、つまり、MINMAXの間でランダムな正の整数を取得するプロシージャは何であるかと思っていました

  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。たとえば、$MINが0で$MAXが9の場合、$RANDOMが32768または32769になることはないため、0〜7の値は8および9の値よりもわずかに高くなります。たとえば、$MINが0で$MAXが9999の場合、バイアスは範囲が増えるにつれて悪化します。0から2767までの数値は、 4/32767、2768〜9999の確率は /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からランダム性を収集するだけです(暗号学的に強力な疑似乱数ジェネレータが必要で、lotsの場合、代わりに/dev/randomを使用することを検討してください時間、またはハードウェアの乱数ジェネレータなど)、10進数ではないすべての文字を削除し、出力を$MAXの長さに折りたたみ、先頭の0を切り取ります。たまたま0しか取得していない場合、$rndは空なので、この場合はrnd0に設定します。結果が範囲外かどうかを確認し、範囲外の場合は繰り返します。 rndは最初は定義されていないので、do ... whileループをエミュレートする精神で、whileループの「本体」をここでガードに強制し、少なくとも1回は本体の実行を強制しました。

ここでは条件1と2を満たしたと思いますが、今度は条件3を台無しにしました。少し遅いです。最大で1秒ほどかかります(運がよければ10分の1秒)。実際には、ループの終了は保証されていません(ただし、時間の増加に伴って終了の確率は1に収束します)。

Bashで、事前に指定された潜在的に大きな範囲内で、不偏ランダム整数を取得する効率的な方法はありますか? (時間が許す限り調査を続けますが、それまでの間、ここにいる誰かがクールなアイデアを持っていると思いました!)

回答表

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

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

    • Perlソリューション
      • プロ:非常にポータブルでシンプル、柔軟
      • 反対:2を超える非常に大きな数の場合は不可32-1
    • Pythonソリューション
      • プロ:シンプルで柔軟性があり、多数でも機能します
      • 反対:移植性が低い
    • zshソリューション
      • プロ:とにかくzshを使用する人に適しています
      • 反対:おそらくさらにポータブルではない
31
Malte Skoruppa

すばらしい回答をありがとうございました。私は共有したい次の解決策に終わりました。

理由と方法についてさらに詳しく説明する前に、ここに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

他の回答者によるすべてのアイデアは素晴らしかった。 terdonJ.F。Sebastian 、および jimmij による回答では、外部ツールを使用して、シンプルで効率的な方法でタスクを実行しました。ただし、移植性を最大化するために真のbashソリューションを選択しました。おそらく少しだけ、bashへの愛着が薄いためです;)

Rameshl0b の回答では、/dev/urandomまたは/dev/randomodと組み合わせて使用​​しました。それは良いことですが、彼らのアプローチには、0から2の範囲のランダムな整数しかサンプリングできないという欠点がありました。8nこのメソッドはバイト、つまり長さ8のビット文字列をサンプリングするため、一部のnの場合は-1。

最後に、 Falco の答えは、これがarbitrary範囲(2の累乗だけでなく)に対してどのように実行できるかについての一般的な考え方を説明しています。基本的に、特定の範囲{0..max}について、次の2のべき乗、つまり、ビットストリングとしてmaxを表すために必要なbitsの正確な数を決定できます。次に、そのビット数だけをサンプリングして、このバイストリングが整数としてmaxより大きいかどうかを確認できます。その場合は繰り返します。 maxを表すのに必要なだけのビットをサンプリングするので、各反復は成功の50%以上の確率を持ちます(最悪の場合50%、最良の場合100%)。したがって、これは非常に効率的です。

私のスクリプトは基本的にFalcoの回答の具体的な実装であり、純粋なbashで書かれており、bashの組み込みのビット単位演算を使用して目的の長さのビット文字列をサンプリングするため、非常に効率的です。さらに、$RANDOMの繰り返しの呼び出しから生じるビット文字列を連結することにより、組み込みの$RANDOM変数を使用することを提案する Eliah Kagan によるアイデアを尊重します。私は実際に/dev/urandom$RANDOMを使用する可能性の両方を実装しました。デフォルトでは、上記のスクリプトは$RANDOMを使用します。 (そして、/dev/urandomを使用する場合は odtr が必要ですが、これらはPOSIXによってサポートされています。)

では、どのように機能するのでしょうか?

私がこれに入る前に、2つの観察:

  1. Bashが2より大きい整数を処理できないことがわかりました63-1。自分で見て:

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

    Bashは整数を格納するために内部的に符号付き64ビット整数を使用しているように見えます。だから、263 それは「循環」し、負の整数を取得します。したがって、2より大きい範囲を取得することは望めません。63-1はランダム関数を使用します。 Bashはそれを処理できません。

  2. minmaxの間の任意の値をmin != 0でサンプリングしたい場合は、代わりに0max-minの間で値をサンプリングし、最終的な結果にminを追加します。 。これは、minおよびmaxnegativeであっても機能しますが、0の絶対値の間の値をサンプリングするように注意する必要があります= max-min。したがって、0と任意の正の整数maxの間のランダムな値をサンプリングする方法に焦点を当てることができます。残りは簡単です。

ステップ1:整数(対数)を表すために必要なビット数を決定します

したがって、与えられた値maxについて、それをビット文字列として表すために必要なビット数を知りたいのです。これは、後でランダムに必要なビットだけをランダムにサンプリングできるようにするためです。これにより、スクリプトが非常に効率的になります。

どれどれ。 nビットを使用するため、値2までを表すことができます-1の場合、任意の値を表すために必要なビット数nxはceiling(log2(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によって言及された idea を使用します。基本的に、$RANDOMは15ビット整数をサンプリングするため、$((RANDOM<<15|RANDOM))を使用して30ビット整数をサンプリングできます。つまり、$RANDOMの最初の呼び出しを15ビット左にシフトし、ビット単位で適用するか、$RANDOMの2番目の呼び出しで適用して、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のビット文字列を読み取ります。前の方法と同様に、非常に多くのバイトをサンプリングして、同等の数のサンプリングbitsがn以上になるようにします。余分な部分を切り落とします。

少なくともnビットを取得するために必要な最小バイト数は、n以上の最小の8の倍数です。つまり、floor((n + 7)/ 8)です。

これは、最大56ビットの整数でのみ機能します。さらに1バイトをサンプリングすると、64ビット整数、つまり最大2の値が得られます64-1、bashは処理できません。

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

ピースをまとめる:arbitrary rangeでランダムな整数を取得します

これでnビットのビット文字列をサンプリングできますが、0からmax一様にランダムまでの範囲の整数をサンプリングしたい場合、maxは任意、必ずしも2のべき乗ではありません。 (それはバイアスを作成するので、私たちはモジュロを使用することはできません。)

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
}

物事をまとめる

最後に、minmaxの間の整数をサンプリングします。ここで、minmaxは任意であり、負であってもかまいません。前述のように、これは簡単なことです。

それをすべてbashスクリプトに入れましょう。いくつかの引数解析を行います... 2つの引数minおよびmax、または1つの引数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

...そして最後に、minmaxの間の値をランダムに均一にサンプリングするには、0max-minの絶対値の間のランダムな整数をサンプリングし、minを最終結果。 :-)

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

echo $(( $(Rand $diff) + min ))

this に触発されて、私は dieharder を使用してこのPRNGをテストおよびベンチマークし、私の発見をここに入れようとするかもしれません。 :-)

8
Malte Skoruppa

here から別の興味深いメソッドを見つけます。

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

これ 1つも良いオプションのようです。ランダムデバイスから4バイトを読み取り、02^32-1の間の符号なし整数としてフォーマットします。

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

それはzshですか?

_max=1000
integer rnd=$(( $(( Rand48() )) * $max ))
_

Rand48(seed)でもシードを使用できます。興味がある場合の詳細な説明については、_man zshmodules_および_man 3 erand48_を参照してください。

6
jimmij

から(2 ^ n)-1までの数値が必要な場合、ここではn mod 8 =を取得するだけでn/8/dev/randomからのバイト。たとえば、ランダムなintの10進数表現を取得するには、次のようにします。

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

nbitsだけを取得する場合は、最初にceiling(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を使用する前に、何をしているかを確認してください。

5
l0b0
$ python -c 'import random as R; print(R.randint(-3, 5**1234))'

pythonは、DebianベースのシステムであるRedhatで使用できます。

5
jfs

外部ツールの使用に反対しない場合、これは要件を満たしているはずです。

Rand=$(Perl -e 'print int(Rand(2**32-1))'); 

上限をパラメータとして取るPerlのRand関数を使用しています。好きなように設定できます。これが抽象的な数学的定義の真のランダム性にどれほど近いかは、このサイトの範囲外ですが、非常に機密性の高い暗号化などに必要でない限り、問題ありません。たぶんそこにもありますが、私は意見を出しません。

3
terdon

希望する最大値以上の最も近い(2 ^ X)-1を取得し、ビット数を取得する必要があります。次に、/ dev/randomを複数回呼び出し、十分になるまですべてのビットを一緒に追加し、多すぎるすべてのビットを切り捨てます。結果の数が最大繰り返し数より大きい場合。最悪の場合、乱数が最大値を下回る可能性が50%を超えるため、(この最悪の場合)平均で2回の呼び出しが行われます。

2
Falco

あなたの答えは興味深いですが、かなり長いです。

任意の大きな数値が必要な場合は、ヘルパーで複数の乱数を結合できます。

# $1 - number of 'digits' of size base
function random_helper()
{
  base=32768
  random=0
  for((i=0; i<$1; ++i)); do
    let "random+=$RANDOM*($base**$i)"
  done
  echo $random
}

問題がバイアスである場合は、それを削除します。

# $1 - min value wanted
# $2 - max value wanted
function random()
{
  MAX=32767
  min=$1
  max=$(($2+1))
  size=$((max-min))
  bias_range=$((MAX/size))
  while
    random=$RANDOM
  [ $((random/size)) -eq $bias_range ]; do :; done
  echo $((random%size+min))
}

これらの機能を結合する

# $1 - min value wanted
# $2 - max value wanted
# $3 - number of 'digits' of size base
function random()
{
  base=32768
  MAX=$((base**$3-1))
  min=$1
  max=$(($2+1))
  size=$((max-min))
  bias_range=$((MAX/size))
  while
    random=$(random_helper)
  [ $((random/size)) -eq $bias_range ]; do :; done
  echo $((random%size+min))
}
0
Adrian