如何随机迭代一个大范围?

时间:2010-03-17 04:30:43

标签: ruby random range loops brute-force

我想随机迭代一个范围。每个值只访问一次,最终将访问所有值。例如:

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用于有效地提供随机排序。

我的问题是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
}

此代码非常幼稚,并且在tried获得更多条目时会很快耗尽内存。

什么样的算法可以完成我想要做的事情?

[Edit1] :我为什么要这样做?我试图耗尽哈希算法的搜索空间,寻找一个寻找部分冲突的N长度输入字符串。我生成的每个数字等同于唯一的输入字符串,熵和全部。基本上,我正在使用custom alphabet“计算”。

[Edit2] :这意味着上面示例中的f(x)是一种生成哈希并将其与常量目标哈希进行比较的方法,用于部分冲突。在调用x之后,我不需要存储f(x)的值,因此内存应该随时间保持不变。

[Edit3 / 4/5/6] :进一步澄清/修复。

[解决方案] :以下代码基于@ bta的解决方案。为简明起见,未显示next_prime。它产生可接受的随机性,并且只访问每个数字一次。有关更多详细信息,请参阅实际帖子。

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

11 个答案:

答案 0 :(得分:11)

我刚刚记得几年前我上过的一个类似的问题;也就是说,在给定非常严格的内存约束的情况下,通过集合(相对)随机迭代(完全耗尽它)。如果我正确地记住了这一点,我们的求解算法是这样的:

  1. 将范围定义为0到 一些数字 N
  2. x[0]
  3. 中生成随机起点 N
  4. 生成小于Q
  5. 的迭代器 N
  6. 通过添加x[n]来生成连续的点 Q 如果需要,可以在前一点和周围环绕。那 是,x[n+1] = (x[n] + Q) % N
  7. 重复,直到生成一个等于起点的新点。
  8. 诀窍是找到一个迭代器,它可以让你遍历整个范围而不会产生两次相同的值。如果我正确记住,任何相对素数NQ都会起作用(数字越接近范围界限,输入越少'随机'。在这种情况下,不是N因子的素数应该有效。您还可以在结果数字中交换字节/半字节,以更改生成的点在N中“跳转”的模式。

    此算法仅需要起点(x[0]),当前点(x[n]),迭代器值(Q)和范围限制(N )要存储。

    也许其他人会记住这个算法并且可以验证我是否正确记住它?

答案 1 :(得分:3)

正如@Turtle回答的那样,你的问题没有解决方案。 @KandadaBoggu和@bta解决方案为您提供随机数,是某些范围是随机的还是非随机的。你得到了一组数字。

但我不知道为什么你关心同一号码的双重发生。如果(0..99**99)是您的范围,那么如果您每秒可以生成10 ^ 10个随机数(如果您有3 GHz处理器和大约4个核心,您在每个CPU周期生成一个随机数 - 这是不可能的,并且红宝石甚至会减慢它的速度,然后耗费所有数字大约需要 10 ^ 180年。你也有大约10 ^ -180的概率,在一整年内会产生两个相同的数字。我们的宇宙可能大约有10 ^ 9年,所以如果你的计算机可以在时间开始时开始计算,那么你将有大约10 ^ -170的概率生成两个相同的数字。换句话说 - 实际上它是不可能的,你不必关心它。

即使您只使用Jaguar(来自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
}
如果你曾经看过“哦,不!”我会给你买啤酒。在你的生活中你的屏幕上:))

答案 2 :(得分:1)

我可能错了,但我不认为如果没有存储某些状态就行。至少,你需要一些州。

即使您每个值只使用一位(此值已尝试是或否),您将需要X / 8字节的内存来存储结果(其中X是最大的数字)。假设您有2GB的可用内存,这将为您留下超过1600万的数字。

答案 3 :(得分: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的一个很好的问题。每个批处理可由独立节点处理。

<强>参考:

Map-reduce in Ruby

答案 4 :(得分:1)

你可以使用shuffle方法随机迭代一个数组

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

答案 5 :(得分:1)

你想要什么叫做&#34;完整循环迭代器&#34; ...

这是最简单版本的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,永不重复如果你改变random_seed,可以是任何东西,或者prime_number,它必须大于,并且不能被sample_size整除,你将获得一个新的随机顺序,但你仍然永远不会得到重复。

答案 6 :(得分:0)

数据库系统和其他大型系统通过将递归排序的中间结果写入临时数据库文件来实现此目的。这样,他们可以对大量记录进行排序,同时在任何时候只在内存中保留有限数量的记录。这在实践中往往很复杂。

答案 7 :(得分: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

基本上,您通过一次随机生成一个数字来构建索引。在最坏的情况下,这将需要足够的内存来存储10 *(位数)。您将只会遇到(0..(10**3))范围内的每个数字,但订单只是伪随机的。也就是说,如果第一个循环设置a=1,那么在看到数百位数变化之前,您将遇到1xx形式的所有三位数字。

另一个缺点是需要手动将函数构造到指定的深度。在你的(0..(99**99))情况下,这可能是一个问题(虽然我想你可以编写一个脚本来为你生成代码)。我确信可能有一种方法可以用一种有状态的,递归的方式重写它,但我无法想到它(想法,任何人?)。

答案 8 :(得分: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

我希望代码评论有助于阐明我原来的问题。

pastebin: full source

注意:PW_LEN下的# options可以更改为较低的数字,以便获得更快的结果。

答案 9 :(得分: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小几个订单,就具有大量的随机性。

归功于@nick-steele@bta方法。

答案 10 :(得分:0)

这并不是一个真正针对 Ruby 的答案,但我希望它被允许。 Andrew Kensler 在他的 "Correlated Multi-Jittered Sampling" 报告中给出了一个 C++“permute()”函数,该函数正是这样做的。

据我所知,他提供的确切函数仅在您的“数组”大小达到 2^27 时才有效,但总体思路可用于任何大小的数组。

我会尽力解释一下。第一部分是您需要一个“对于任何 2 次幂大小的域”可逆的散列。考虑x = i + 1。不管 x 是什么,即使你的整数溢出,你也可以确定 i 是什么。更具体地说,您始终可以从 x 的底部 n 位确定 i 的底部 n 位。加法是可逆的散列运算,乘以奇数也是可逆的,正如对常数进行按位异或一样。如果您知道特定的 2 的幂域,则可以在该域中打乱位。例如。 x ^= (x & 0xFF) >> 5) 对 16 位域有效。您可以使用掩码指定该域,例如mask = 0xFF,您的哈希函数变为 x = hash(i, mask)。当然,您可以在该哈希函数中添加“种子”值以获得不同的随机化。肯斯勒在论文中列出了更多有效的操作。

所以你有一个可逆函数 x = hash(i, mask, seed)。问题是,如果对索引进行哈希处理,最终得到的值可能会大于数组大小,即“域”。你不能只对这个求模,否则会发生冲突。

可逆散列是使用称为“循环行走”的技术的关键,在“Ciphers with Arbitrary Finite Domains"”中介绍。因为散列是可逆的(即 1 比 1),您可以重复应用相同的散列直到你的散列值小于你的数组!因为你应用了相同的散列,而且映射是一对一的,你最终得到的任何值都将映射回一个索引,所以你没有冲突. 因此,对于 32 位整数(伪代码),您的函数可能如下所示:

fun permute(i, length, seed) {
  i = hash(i, 0xFFFF, seed)
  while(i >= length): i = hash(i, 0xFFFF, seed)
  return i
}

可能需要大量哈希才能到达您的域,因此 Kensler 做了一个简单的技巧:他将哈希保持在下一个 2 的幂的域内,这使得它只需要很少的迭代(平均约 2 次) ,通过屏蔽掉不必要的位。最终的算法如下所示:

fun next_pow_2(length) {
  # This implementation is for clarity.
  # See Kensler's paper for one way to do it fast.
  p = 1
  while (p < length): p *= 2
  return p
}

permute(i, length, seed) {
  mask = next_pow_2(length)-1
  i = hash(i, mask, seed) & mask
  while(i >= length): i = hash(i, mask, seed) & mask
  return i
}

就是这样!显然,这里重要的是选择一个好的散列函数,Kensler 在论文中提供了它,但我想分解解释。如果你想每次都有不同的随机排列,你可以向 permute 函数添加一个“种子”值,然后传递给哈希函数。