9


7

大きな範囲をランダムに反復するにはどうすればよいですか?

範囲をランダムに繰り返したいと思います。 各値は1回だけアクセスされ、すべての値が最終的にアクセスされます。 例えば:

class Array
    def shuffle
        ret = dup
        j = length
        i = 0
        while j > 1
            r = i + rand(j)
            ret[i], ret[r] = ret[r], ret[i]
            i += 1
            j -= 1
        end
        ret
    end
end

(0..9).to_a.shuffle.each{|x| f(x)}

ここで、「f(x)」は各値を操作する関数です。 Fisher-Yates shuffleは、ランダムな順序を効率的に提供するために使用されます。

私の問題は、「シャッフル」が配列で動作する必要があることです。これは、天文学的に*大きな数で作業しているのでクールではありません。 Rubyは、巨大な配列を作成しようとして、すぐに大量のRAMを消費します。 `(0..9)`を `(0..99 * 99)`に置き換えると想像してください。 これは、次のコードが機能しない理由でもあります。

tried = {} # store previous attempts
bigint = 99**99
bigint.times {
    x = rand(bigint)
    redo if tried[x]
    tried[x] = true
    f(x) # some function
}

このコードは非常に単純であり、「試行」がより多くのエントリを取得すると、すぐにメモリ不足になります。

私がやろうとしていることをどのようなアルゴリズムで達成できますか?

  • [Edit1] *:なぜこれをしたいのですか? 部分的な衝突を探すN長さの入力文字列のハッシュアルゴリズムの検索スペースを使い果たしようとしています。 生成する各数値は、一意の入力文字列、エントロピー、およびすべてに相当します。 基本的に、http://thebaconoflife.com/b/2009/08/15/base-64-url-encodingcounting/ [カスタムアルファベット]を使用して「カウント」しています。

  • [Edit2] *:これは、上記の例の `f(x)`がハッシュを生成し、それを部分的な衝突のために定数のターゲットハッシュと比較するメソッドであることを意味します。 `f(x)`を呼び出した後、 `x`の値を保存する必要はないので、メモリは時間とともに一定に保たれます。

  • [Edit3 / 4/5/6] *:さらなる説明/修正。

  • [ソリューション] *:次のコードは、@ btaのソリューションに基づいています。 簡潔にするために、「next_prime」は表示されていません。 許容可能なランダム性を生成し、各番号に1回だけアクセスします。 詳細については実際の記事を参照してください。

N = size_of_range
Q = ( 2 * N / (1 + Math.sqrt(5)) ).to_i.next_prime
START = rand(N)

x = START
nil until f( x = (x + Q) % N ) == START # assuming f(x) returns x

10 Answer


11


数年前に受講したクラスで同様の問題を思い出しました。つまり、非常に厳しいメモリ制約が与えられている場合、セットを(比較的)ランダムに反復します(完全に使い果たします)。 これを正しく覚えている場合、ソリューションアルゴリズムは次のようになりました。

  1. 範囲を0からある数_`N`_に定義します

  2. `N`_の中にランダムな開始点`x [0] `_を生成します

  3. イテレータ_`Q`_を `N`未満生成します

  4. 前の行に「Q」を追加して、連続したポイント_`x [n] _を生成します 必要に応じてポイントして折り返します。 つまり、 `x [n + 1] =(x [n] + Q)%N

  5. 開始点に等しい新しい点を生成するまで繰り返します。

トリックは、同じ値を2回生成せずに範囲全体をトラバースできるイテレータを見つけることです。 正しく覚えていれば、比較的素数の N`と Q`が機能します(範囲の境界に数値が近いほど、入力は 'ランダム’になりません)。 その場合、「N」の因子ではない素数が機能するはずです。 また、結果の数値のバイト/ニブルを交換して、生成されたポイントが「N」で「ジャンプ」するパターンを変更することもできます。

このアルゴリズムでは、開始点( x [0])、現在の点( x [n])、反復子の値( Q)、および範囲の制限(` N`)のみを保存する必要があります。

おそらく他の誰かがこのアルゴリズムを覚えており、それを正しく覚えているかどうかを確認できますか?


3


@Turtleが答えたように、問題には解決策がありません。 @KandadaBogguと@btaのソリューションは、ランダムな数の範囲をランダムに与えます。 数字のクラスターを取得します。

しかし、なぜ同じ数の二重発生を気にするのかわかりません。 `(0..99 * 99)`が範囲の場合、1秒あたり10 ^ 10の乱数を生成できる場合(3 GHzプロセッサとCPUサイクルごとに1つの乱数を生成する約4コアがある場合) -これは不可能であり、ルビーはそれを大幅に遅くします)、すべての数字を使い果たすのに 10 ^ 180年*かかります。 また、1年の間に2つの同じ数字が生成される可能性は10 ^ -180程度です。 私たちの宇宙にはおそらく10 ^ 9年ほどあるので、時間の始まりにコンピューターが計算を開始できれば、10 ^ -170の確率で2つの同一の数字が生成されます。 言い換えれば-*実際には不可能*であり、あなたはそれを気にする必要はありません。

この1つのタスクだけでJaguar(http://www.top500.org [www.top500.org]スーパーコンピューターのトップ1)を使用する場合でも、すべての数値を取得するには10 ^ 174年が必要です。

あなたが私を信じていないなら、試してください

tried = {} # store previous attempts
bigint = 99**99
bigint.times {
  x = rand(bigint)
  puts "Oh, no!" if tried[x]
  tried[x] = true
}

「ああ、いや!」と一度も見られたらビールを買います。あなたの人生の間にあなたの画面上で:)


1


私は間違っている可能性がありますが、これは何らかの状態を保存しなくても実行できるとは思いません。 少なくとも、何らかの状態が必要になります。

値ごとに1ビットのみを使用した場合(この値はyesまたはnoで試行されました)、結果を保存するためにX / 8バイトのメモリが必要になります(Xは最大数)。 2GBの空きメモリがあると仮定すると、1600万を超える数が残されます。


1


以下に示すように、管理可能なバッチに範囲を分割します。

def range_walker range, batch_size = 100
  size = (range.end - range.begin) + 1
  n = size/batch_size
  n.times  do |i|
    x = i * batch_size + range.begin
    y = x + batch_size
    (x...y).sort_by{rand}.each{|z| p z}
  end
  d = (range.end - size%batch_size + 1)
  (d..range.end).sort_by{rand}.each{|z| p z }
end

処理するバッチをランダムに選択することにより、ソリューションをさらにランダム化できます。

  • PS:*これは、map-reduceに適した問題です。 各バッチは、独立したノードで動作できます。

参照:


1


シャッフル方法でランダムに配列を繰り返すことができます

a = [1,2,3,4,5,6,7,8,9]
a.shuffle!
=> [5, 2, 8, 7, 3, 1, 6, 4, 9]


1


「フルサイクルイテレータ」と呼ばれるものが必要です…​

これは、ほとんどの用途に最適な最も単純なバージョンのpsudocodeです…​

function fullCycleStep(sample_size, last_value, random_seed = 31337, prime_number = 32452843) {
if last_value = null then last_value = random_seed % sample_size
    return (last_value + prime_number) % sample_size
}

これを次のように呼び出す場合:

sample = 10
For i = 1 to sample
    last_value = fullCycleStep(sample, last_value)
    print last_value
next

ランダムに生成され、10個すべてをループし、繰り返されることはありません。それでも重複することはありません。


0


データベースシステムおよびその他の大規模システムは、再帰ソートの中間結果を一時データベースファイルに書き込むことでこれを行います。 このようにして、一度に限られた数のレコードのみをメモリに保持しながら、膨大な数のレコードをソートできます。 これは実際には複雑になる傾向があります。


0


あなたの注文はどのくらい「ランダム」でなければなりませんか? 特定の入力分布が必要ない場合は、このような再帰的なスキームを試して、メモリ使用量を最小限に抑えることができます。

def gen_random_indices
  # Assume your input range is (0..(10**3))
  (0..3).sort_by{rand}.each do |a|
    (0..3).sort_by{rand}.each do |b|
      (0..3).sort_by{rand}.each do |c|
        yield "#{a}#{b}#{c}".to_i
      end
    end
  end
end

gen_random_indices do |idx|
  run_test_with_index(idx)
end

基本的に、一度に1桁ずつランダムに生成してインデックスを構築します。 最悪のシナリオでは、これには10 (桁数)を格納するのに十分なメモリが必要です。 範囲 `(0 ..(10 * 3))`内のすべての数値に1回だけ遭遇しますが、順序は擬似乱数にすぎません。 つまり、最初のループで「a = 1」が設定されている場合、数百桁の変化が見られる前に、「1xx」という形式の3桁の数字がすべて表示されます。

もう1つの欠点は、指定された深さまで関数を手動で構築する必要があることです。 `(0 ..(99 ** 99))`の場合、これは問題になる可能性があります(ただし、スクリプトを作成してコードを生成できると思います)。 おそらくこれをステートフルで再帰的な方法で書き直す方法があるとは思いますが、頭から離れて考えることはできません(アイデア、誰か?)。


0


  • [編集] *:@klewと@Turtleの答えを考慮して、私が期待できる最善の方法は、ランダムな(またはランダムに近い)数のバッチです。

'' '' '

これは、KandadaBogguのソリューションに似た何かの再帰的な実装です。 基本的に、サーチスペース(範囲として)は、N個の同じサイズの範囲を含む配列に分割されます。 各範囲は、新しい検索スペースとしてランダムな順序でフィードバックされます。 これは、範囲のサイズが下限に達するまで続きます。 この時点で、範囲は配列に変換され、シャッフルされ、チェックされるのに十分小さいです。

それは再帰的ですが、まだスタックを爆破していません。 代わりに、約10 ^ 19のキーよりも大きいサーチスペースをパーティション化しようとするとエラーになります。 数値が大きすぎて「long」に変換できないことに関係しています。 おそらく修正できます:

# partition a range into an array of N equal-sized ranges
def partition(range, n)
    ranges = []
    first = range.first
    last = range.last
    length = last - first + 1
    step = length / n # integer division
    ((first + step - 1)..last).step(step) { |i|
        ranges << (first..i)
        first = i + 1
    }
    # append any extra onto the last element
    ranges[-1] = (ranges[-1].first)..last if last > step * ranges.length
    ranges
end

コードのコメントが、私の元の質問に光を当てる助けになることを願っています。

注:より速い結果を得るために、「#オプション」の下の「PW_LEN」をより小さな数値に変更できます。


0


以下のような非常に大きなスペースの場合

space = -10..1000000000000000000000

このメソッドを「Range」に追加できます。

class Range

  M127 = 170_141_183_460_469_231_731_687_303_715_884_105_727

  def each_random(seed = 0)
    return to_enum(__method__) { size } unless block_given?
    unless first.kind_of? Integer
      raise TypeError, "can't randomly iterate from #{first.class}"
    end

    sample_size = self.end - first + 1
    sample_size -= 1 if exclude_end?
    j = coprime sample_size
    v = seed % sample_size
    each do
      v = (v + j) % sample_size
      yield first + v
    end
  end

protected

  def gcd(a,b)
    b == 0 ? a : gcd(b, a % b)
  end

  def coprime(a, z = M127)
    gcd(a, z) == 1 ? z : coprime(a, z + 1)
  end

end

それで

space.each_random { |i| puts i }

729815750697818944176
459631501395637888351
189447252093456832526
919263002791275776712
649078753489094720887
378894504186913665062
108710254884732609237
838526005582551553423
568341756280370497598
298157506978189441773
27973257676008385948
757789008373827330134
487604759071646274309
217420509769465218484
947236260467284162670
677052011165103106845
406867761862922051020
136683512560740995195
866499263258559939381
596315013956378883556
326130764654197827731
55946515352016771906
785762266049835716092
515578016747654660267
...

スペースがM127よりも数桁小さい限り、かなりのランダム性があります。

このアプローチについては、https://stackoverflow.com/users/3196360/nick-steele [@ nick-steele]およびhttps://stackoverflow.com/users/79566/bta[@bta]に感謝します。