9

这个较早的堆栈溢出问题的启发,我一直在考虑如何在 python 中随机交错可迭代对象,同时保留每个可迭代对象中元素的顺序。例如:

>>> 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()在它们上调用,这意味着它不能与可迭代对象一起使用。它还具有清空源列表 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 中随机交错迭代,保留元素的原始顺序,这不需要提前知道迭代的长度并且不需要复制迭代?

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

4

3 回答 3

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)))
于 2012-05-18T07:27:36.597 回答
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,...
    ^   ^
于 2012-05-18T07:23:29.990 回答
3

我很满意aix提供的解决方案满足问题的要求。然而,在阅读了Mark Byers 的评论后,我想看看这个解决方案是多么“不公平”。

此外,在我写完这个问题后的某个时候,堆栈溢出用户 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))]

我还进一步增强了我自己的解决方案,使其不依赖于支持的论点 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)

然后,我测试了由 FJ 编写并在我上面的问题中复制的原始问题的公认解决方案,以及 aix、EOL 和我自己的解决方案。该测试涉及将 30000 个元素的列表与单个元素列表(哨兵)交错。我重复了测试 1000 次,下表显示了每种算法在交织后哨兵的最小值、最大值和平均索引,以及所用的总时间。我们期望一个“公平”的算法产生一个大约的平均值。15,000:

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

从结果可以看出,FJ、EOL 和 srgerg 的每个算法都产生了表面上“公平”的结果(至少在给定的测试条件下)。然而,aix 的算法始终将标记放在结果的前 10 个元素中。我重复了几次实验,结果相似。

所以马克拜尔斯被证明是正确的。如果需要真正的随机交织,则需要提前知道源迭代的长度,或者需要制作副本以便确定长度。

于 2012-05-19T03:23:46.997 回答