交错交错多个迭代,同时在python中保留它们的顺序

时间:2012-05-18 07:19:27

标签: python

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>

修改:请注意,与原始问题一样,我不需要随机化是公平的。

3 个答案:

答案 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被证明是正确的。如果需要真正的随机交织,则需要提前知道源迭代的长度,或者需要进行复制以确定长度。