在长迭代器上以几乎均匀采样的随机顺序进行迭代,而无需占用大量内存

时间:2018-12-19 10:54:52

标签: python iterator permutation

我要遍历0N-1范围内的整数,其中N是一个大数。 使用for i in range(N):可以轻松完成。

但是,我想以随机顺序迭代数字。 也可以使用以下类似方法轻松完成此操作:

from random import shuffle
a = list(range(N))
shuffle(a)
for i in a:
      do_something(i)

此方法的问题在于它需要将整个数字列表存储在内存中。 (shuffle(range(N))引发错误)。对于我的目的,这对于大型N来说是不切实际的。

我想有一个对象,它是一个迭代器(就像range(N)一样),它不将所有数字都存储在内存中(同样,像range(N)一样),并且可以在随机顺序。

现在,当我说“随机顺序”时,我的意思是,该顺序是从(0,1,...,N-1)的所有排列集合的均匀分布中采样的。我知道这个数字可能很大(N!),因此,如果迭代器需要表示它使用的排列,那么它在内存中就必须很大。

因此,在某种意义上我没有定义,我可以确定“看起来实际上是均匀分布,但看起来像均匀分布”的“随机顺序”。

如果我有这样的迭代器,这就是我的操作方式:

a = random_order_range(N) # this object takes memory much smaller than then factorial of N
for i in a:
    do_something(i)

有什么想法可以做到吗?


EDIT1:

实际上,我真正感兴趣的是,如果可能的话,内存消耗将甚至比~N还要少……对于某些O(k*N),也许像k比1小得多。

2 个答案:

答案 0 :(得分:2)

import functools, random, itertools  
from collections import deque
import random
from bloom_filter import BloomFilter

def random_no_repeat(random_func, limit):
    already_returned = BloomFilter()
    count = 0
    while True:
        i = random_func()
        if i not in already_returned:
            count += 1
            already_returned.add(i)
            yield i
            if (count == limit):
                break

def count_iter_items(iterable):
    counter = itertools.count()
    deque(itertools.zip_longest(iterable, counter), maxlen=0)  # (consume at C speed)
    return next(counter)

N = 1e5
random.seed(0)
random_gen = random_no_repeat(functools.partial(random.randint, 0, int(N)))

for index, i in  enumerate(random_gen):
    print(index, i)

答案 1 :(得分:1)

我不太确定空间和时间要求,但是应该比N!少得多-通过固定限制lowhigh以及setseen内部对象中,它也不需要花太长时间就可以画出一个数字,然后当您简单地从N开始暴力并检查是否在seen中时:

import random 

def random_range(N): 
    seen = set()
    low = 0
    high = N
    seen = set()
    while low < high:
        k = random.choice(range(low,high))
        if k in seen:
            # already drafted - try again
            continue
        else:
            yield k

            seen.add(k)

            # fix lower
            while low in seen:
                seen.remove(low)
                low += 1

            # fix upper
            while high-1 in seen:
                seen.remove(high-1)
                high -= 1

for i in random_range(20):
    print(i, end = ", ")

输出:

7, 2, 5, 18, 11, 3, 6, 10, 14, 9, 15, 17, 19, 0, 16, 4, 1, 12, 13, 8,

如果您将N插入为2 ^ 63,则seen集在缩小之前会变得很大,因为达到低位或高位的可能性很小-这就是最多内存的原因消费。

seen相对于range(low,high)而言,运行时变得更糟,因为它可能需要2000年才能继续命中一个不在seen中的随机数:

# pseudo 
seen = { 1-99999,100001-99999999999 } 
low = 0
high = 99999999999+2

这不是“可归约的”,range(0, 99999999999+2)中只剩下3个数字-但是达到这样的目标的机会也很小。

您的选择; o)