受this earlier stack overflow question的启发我一直在考虑如何在python中随机交错迭代,同时保留每个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,并且接受的解决方案是:
>>> c = [x.pop(0) for x in random.sample([a]*len(a) + [b]*len(b), len(a)+len(b))]
但是,此解决方案仅适用于两个列表(尽管可以轻松扩展),并且依赖于a和b是列表的事实,以便可以在它们上调用pop()
和len()
,意味着它不能与iterables一起使用。它还有清空源列表a和b的不幸副作用。
为原始问题提供的替代答案会获取源列表的副本以避免修改它们,但这对我来说效率低下,特别是如果源列表相当大。备用答案也使用len()
,因此不能仅用于迭代。
我编写了自己的解决方案,适用于任意数量的输入列表,不会修改它们:
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中随机交织迭代,保留元素的原始顺序,这不需要提前知道迭代的长度,也不需要复制迭代? / p>
修改:请注意,与原始问题一样,我不需要随机化是公平的。
答案 0 :(得分:10)
以下是使用生成器执行此操作的一种方法:
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)))
答案 1 :(得分:3)
如果你想要适合“公平”,那就不是了。
想象一下,您有一个包含一百万个项目的列表,另一个只包含两个项目。 “公平”随机化将使短名单中的第一个元素出现在约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,...
^ ^
答案 2 :(得分:3)
我很满意aix提供的解决方案符合问题的要求。但是,在阅读comments by Mark Byers之后,我想知道解决方案是多么“不公平”。
此外,在我写完这个问题后的某个时候,堆栈溢出用户EOL将another solution发布到original question,这会产生“合理”的结果。 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))]
我还进一步增强了我自己的解决方案,以便它不依赖于支持len()
的参数,但确实复制了源迭代:
def interleave(*args):
iters = sum(([iter(list_arg)]*len(list_arg) for list_arg in map(list, args)), [])
random.shuffle(iters)
return map(next, iters)
或者,用不同的方式写成:
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个元素中。我重复了几次实验,结果相似。
因此Mark Byers被证明是正确的。如果需要真正的随机交织,则需要提前知道源迭代的长度,或者需要进行复制以确定长度。