【问题标题】:Yield a random permutation from a list of generators of known length从已知长度的生成器列表中产生随机排列
【发布时间】:2021-03-22 03:15:05
【问题描述】:

我有一系列生成器,它们产生需要合理内存量的对象(它们是 ipaddress.IPv4Network 实例,从它们产生一个完整的 ipaddress.IPv4Address 实例)。

gens = [a, b, c, ...]

每个生成器都有确定数量的元素将产生,例如:

gen_lens = [17000000, 1024, 8192, ...]

我想以随机顺序获取长度为n 的成批生成值。任何生成器中的每个项目只能选择一次。

我目前的想法是获取可以产生的可能元素的总数(等于最大数组索引 - 1),然后使用类似 Fisher-Yates-Knuth 算法的方法以随机顺序遍历此列表,产生给定随机索引的项目:

random_indexes = random.shuffle(range(0, sum(gen_lens)))
for i in random_indexes:
    # some windowing logic here to check which generator we should get from and set index appropriately, x = generator index, y = i - sum(gen_lens[0:x])
    yield gens[x][y]

所以最终结果是,我有一个新的生成器,它将从我的输入生成器中产生所有元素的随机排列,而不必存储我的子生成器所产生的结果 all .

它仍然需要构建一个索引列表,当您拥有数百万个索引时,这是相当昂贵的。有办法解决吗?任何人都可以提出更好的方法吗?

【问题讨论】:

  • 不确定我是否 100% 理解您的问题,但感觉 itertool.chain 可能是前进的方向。您可以将可迭代对象连接在一起,而无需支付连接大型可迭代对象的正常费用。 docs.python.org/3/library/…
  • @scotty3785 我认为这有助于简化使用随机索引抓取元素!但它没有解决随机排列部分。
  • OP,您的提案似乎没有考虑到以下条件:“来自任何生成器的每个项目只能选择一次。”一批。我的理解正确吗?
  • 谢谢@AajKaal - 我相信是这样,因为我使用的是所有可能索引的离散列表,因此不应选择来自任何生成器的重复项。我真的应该注意到,这些生成器实际上是可下标的,这根本不是 Python 生成器的典型特征......

标签: python python-3.x random permutation


【解决方案1】:

建议:应该使用二维索引。由于事先为第二维生成索引很昂贵,我一次只为一个生成索引

gens = [a, b, c, ...]
gen_lens = [17000000, 1024, 8192, ...]
shuffled_gens_indexes = list(range(len(gens)))
random.shuffle(shuffled_gens_indexes)
for gens_index in shuffled_gens_indexes:
    shuffled_gen_items_indexes = list(range(gen_lens[gens_index]))
    random.shuffle(shuffled_gen_items_indexes) 
    for gen_items_index in shuffled_gen_items_indexes:
        yield gens[gens_index][gen_items_index]

这非常简单,只是给出了一个特定的项目 一次随机选择生成器。

【讨论】:

  • 这真的很好 - 如果随机化是跨越所有生成器会更好,我认为这可以通过我在问题中已经提出的算法来实现 (i - sum(gen_lens[0:x]) - 但作为你提到,这确实意味着我们必须有一个与所有生成器的总长度一样长的列表。
  • 再想一想,以这种方式为每个生成器生成一个混洗的索引列表将导致您必须存储和混洗一个长达max(gen_lens) 的列表,正如我们所见,这是相当大的。
【解决方案2】:

这就是我最后所做的。我相信这个解决方案比https://stackoverflow.com/a/65240594/1014237 更好,因为它避免使用random.shuffle,所以它从不存储太多元素(即它只存储多达batch_length 数量的随机索引,而不是多达max(gen_lens)。生成随机索引的工作只在需要时进行。

def get_random_element(data, data_length):
    pos = data_length
    while pos > 0:
        idx = random.randrange(start=0, stop=pos)
        pos -= 1
        if idx != pos:
            data[pos], data[idx] = data[idx], data[pos]
        yield data[pos]


def get_random_idx_generator(n):
    # Create a generator of random indexes, n long
    return get_random_element(list(range(n)), n)

我正在使用itertools.islice 从这个生成器中使用,所以我只存储在给定时刻我需要的随机索引。该函数还使用数据列表的索引和长度来确定需要读取的数据。

# Yield a batch_size long list of random IPs, using the random idx generator
def get_randomized_ips_batch(ipnetworks_list, ipnetworks_list_lens,
                             random_idx_generator, batch_size=1024,
                             as_int=False) -> Iterator[Union[ipaddress.IPv4Address, int]]:
    random_indexes_batch = list(itertools.islice(random_idx_generator, batch_size))
    # Figure out which ipnetwork_list our index is pointing to and yield it
    for idx in random_indexes_batch:
        cumulative_len = 0
        gen_idx = 0
        for ipnetwork_len in ipnetworks_list_lens:
            if idx - cumulative_len >= ipnetwork_len:
                cumulative_len += ipnetwork_len
                gen_idx += 1
                continue
            else:
                addr = ipnetworks_list[gen_idx][idx - cumulative_len - 1]
                yield int(addr) if as_int else addr
                break

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多