【问题标题】:Efficiently yield elements from large list in (pseudo) random order以(伪)随机顺序有效地从大列表中产生元素
【发布时间】:2018-08-17 16:40:34
【问题描述】:

我正在尝试展开一些嵌套循环,以便(可能)以牺牲内存为代价获得更好的性能。在我的场景中,我最终会得到一个大约 300M 元素(元组)的列表,我必须以(或多或少)随机顺序产生这些元素。

在这个数量级上,random.shuffle(some_list) 真的不再适合了。

下面的例子说明了这个问题。请注意,在 x86_64 Linux 和 CPython 3.6.4 上,它将占用大约 11 GB 的内存。

def get_random_element():
    some_long_list = list(range(0, 300000000))
    for random_item in some_long_list:
        yield random_item

到目前为止,我的想法是在每次迭代中简单地生成一个随机索引,并从列表中(无限期地)产生随机选择的元素。它可能会多次生成某些元素并完全跳过其他元素,这是值得考虑的权衡。

在合理的内存和 CPU 时间范围内,我还有哪些其他选项可能只产生列表中的每个元素一次?

【问题讨论】:

  • 你有没有考虑过使用random.choice或其他类似的功能?

标签: python python-3.x random generator


【解决方案1】:

这里是 Fisher-Yates-Knuth 就地抽样 (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)

内存稳定~4Gb(是的,我使用的是 100000000)

# Fisher-Yates-Knuth sampling, in-place Durstenfeld version

import numpy as np

def swap(data, posA, posB):
    if posA != posB:
        data[posB], data[posA] = data[posA], data[posB]

def get_random_element(data, datalen):
    pos = datalen

    while pos > 0:
        idx = np.random.randint(low=0, high=pos) # sample in the [0...pos) range

        pos -= 1
        swap(data, idx, pos)

        yield data[pos]


length = 100000000
some_long_list = list(range(0, length))

gen = get_random_element(some_long_list, length)

for k in range(0, length):
    print(next(gen))

更新

为了速度,您可能还想内联 swap()

【讨论】:

  • 感谢您的出色回答。是的,我正在交换内联 - 它删除了一个额外的函数调用。我也在尝试将我的元组(整数)放入一个 numpy 数组而不是 Python 列表(“稍微”更有内存效率),但是上面的交换策略似乎不起作用。但这只是一个细节;)
  • 是的,numpy 不支持上述交换策略:stackoverflow.com/q/14933577/1672565
  • @s-m-e 是的,必须区分视图和副本。其他要考虑的策略是: 1. 捆绑 - 每个 get_random_element() 调用返回样本数组。 2. get_random_element() 主要有两个操作,一个是取回采样值,另一个是交换元素和调整位置。可能值得考虑将其拆分(尤其是在进行分组采样时)并在不同的线程中进行。可能会花费您另一份采样值(或一些锁,甚至是无锁结构),但刚刚返回的堆上的交换可以与主处理循环并行运行。
猜你喜欢
  • 1970-01-01
  • 2014-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-08
  • 2011-10-27
  • 1970-01-01
  • 2013-12-10
相关资源
最近更新 更多