我要遍历0
到N-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小得多。
答案 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!
少得多-通过固定限制low
和high
以及set
的seen
内部对象中,它也不需要花太长时间就可以画出一个数字,然后当您简单地从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)