在python中随机交错多个iterable,同时保持它们的顺序

在python中随机交错多个iterable,同时保持它们的顺序,python,Python,受此启发,我一直在考虑如何在python中随机交错iterable,同时保持每个iterable中元素的顺序。例如: >>> def interleave(*iterables): ... "Return the source iterables randomly interleaved" ... <insert magic here> >>> interleave(xrange(1, 5), xrange(5, 10), xran

受此启发,我一直在考虑如何在python中随机交错iterable,同时保持每个iterable中元素的顺序。例如:

>>> def interleave(*iterables):
...     "Return the source iterables randomly interleaved"
...     <insert magic here>
>>> interleave(xrange(1, 5), xrange(5, 10), xrange(10, 15))
[1, 5, 10, 11, 2, 6, 3, 12, 4, 13, 7, 14, 8, 9]
但是,此解决方案仅适用于两个列表(尽管可以轻松扩展),并且依赖于a和b是列表的事实,因此可以对它们调用
pop()
len()
,这意味着它不能与iterables一起使用。它还有一个不幸的副作用,就是清空源列表a和b

为原始问题提供的备选答案复制源列表以避免修改它们,但这让我觉得效率低下,特别是当源列表相当大时。备选答案还使用了
len()
,因此不能仅用于iterables

我编写了自己的解决方案,可用于任意数量的输入列表,并且不会修改它们:

def interleave(*args):
    iters = [i for i, b in ((iter(a), a) for a in args) for _ in xrange(len(b))]
    random.shuffle(iters)
    return map(next, iters)
但是这个解决方案也依赖于列表中的源参数,这样就可以对它们使用
len()

那么,有没有一种有效的方法可以在python中随机交错iterables,保持元素的原始顺序,而不需要提前知道iterables的长度,也不需要复制iterables

编辑:请注意,与原始问题一样,我不需要随机性来公平。

如果你想让fit“公平”,就不需要

假设您有一个包含一百万个项目的列表,另一个仅包含两个项目。“公平”随机化将使短列表中的第一个元素出现在大约300000个索引处

a,a,a,a,a,a,a,...,a,a,a,b,a,a,a,....
                        ^
但是在你知道列表的长度之前,没有办法提前知道

如果您仅以50%(1/n)的概率从每个列表中提取,则可以在不知道列表长度的情况下完成,但您将得到以下结果:

a,a,b,a,b,a,a,a,a,a,a,a,a,a,a,a,...
    ^   ^

以下是使用生成器执行此操作的一种方法:

import random

def interleave(*args):
  iters = map(iter, args)
  while iters:
    it = random.choice(iters)
    try:
      yield next(it)
    except StopIteration:
      iters.remove(it)

print list(interleave(xrange(1, 5), xrange(5, 10), xrange(10, 15)))

我对aix提供的解决方案满足问题的要求感到满意。然而,在读了这篇文章之后,我想看看这个解决方案有多“不公平”

此外,在我写了这个问题之后的某个时候,堆栈溢出用户EOL发布到了,这会产生一个“公平”的结果。EOL的解决方案是:

>>> a.reverse()
>>> b.reverse()
>>> [(a if random.randrange(0, len(a)+len(b)) < len(a) else b).pop()
...     for _ in xrange(len(a)+len(b))]
或者,用不同的方式写:

def interleave(*args):
    iters = [i for i, j in ((iter(k), k) for k in map(list, args)) for _ in j]
    random.shuffle(iters)
    return map(next, iters)
然后,我测试了被接受的原始问题的解决方案,该解决方案由F.J编写,并在上面的问题中复制到aix、EOL和我自己的解决方案中。测试涉及将30000个元素的列表与单个元素列表(sentinel)交错。我重复了1000次测试,下表显示了每种算法交织后哨兵的最小、最大和平均索引,以及所用的总时间。我们预计“公平”算法的平均值约为15000:

algo    min             max             mean            total_seconds
----    ---             ---             ----            -------------
F.J:    5               29952           14626.3         152.1
aix:    0               8               0.9             27.5
EOL:    45              29972           15091.0         61.2
srgerg: 23              29978           14961.6         18.6
从结果中可以看出,F.J、EOL和srgerg的每个算法都产生表面上“公平”的结果(至少在给定的测试条件下)。然而,aix算法总是将哨兵放在结果的前10个元素中。我重复了几次实验,结果都差不多


因此,马克·拜尔斯被证明是正确的。如果需要真正的随机交错,则需要提前知道源iterables的长度,或者需要制作副本以确定长度。

与原始问题一样,我不需要为了公平而进行随机。我很乐意接受“不公平”的随机选择。谢谢马克,如果在现实世界中这样做的话,我理解答案的公平性是一个重要的考虑因素。但是,在这种情况下,我只需要一个随机的解决方案,因此短列表中的项目可能(而且确实必须可能)出现在结果列表中的任何位置。@srgerg:这两种方法都有可能,而b中的项目可能出现在列表中的任何位置。但“公平”的方法使其成为可能。用你接受的方法从索引100后的b中得到一个元素是极不可能的,尽管我同意从技术上讲这不是不可能的。啊,现在我完全理解你所说的。好极了!我在这个问题中添加了一些评论,说明了在不知道输入长度的情况下,解决方案是多么“不公平”。再次感谢。+1,尽管使用
\u stop
的解决方案不太好。也许
try:val=…
\n
除了StopIteration:iters.pop(i)
\n
否则:yield val
会更干净。@glglglgl:。我刚刚编辑成答案的版本是我最喜欢的版本。很好的答案。请注意,使用try-except比不使用try-except的等效解决方案慢15%(当我在CPython 2.7上尝试它时)。+1:洗牌迭代器是一个好主意!然而,我希望列表理解表达式更容易阅读。我还添加了一个更直接(可能更快)的代码版本。
def interleave(*args):
    iters = [i for i, j in ((iter(k), k) for k in map(list, args)) for _ in j]
    random.shuffle(iters)
    return map(next, iters)
algo    min             max             mean            total_seconds
----    ---             ---             ----            -------------
F.J:    5               29952           14626.3         152.1
aix:    0               8               0.9             27.5
EOL:    45              29972           15091.0         61.2
srgerg: 23              29978           14961.6         18.6