在python中随机交错多个iterable,同时保持它们的顺序
受此启发,我一直在考虑如何在python中随机交错iterable,同时保持每个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
>>> 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