【问题标题】:Interleaving multiple iterables randomly while preserving their order in python随机交错多个可迭代对象,同时在 python 中保留它们的顺序
【发布时间】:2012-05-25 18:34:56
【问题描述】:

受到this earlier stack overflow question 的启发,我一直在考虑如何在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 中随机交错迭代,保留元素的原始顺序,不需要提前知道迭代的长度,也不需要复制迭代?

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

【问题讨论】:

    标签: python


    【解决方案1】:

    这是使用生成器的一种方法:

    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,虽然_stop 的解决方案不是很好。也许try: val=... \n except StopIteration: iters.pop(i) \n else: yield val 会更干净。
    • @glglgl:我一直在尝试各种方法来表达生成器。我刚刚编辑到答案中的版本是我最喜欢的版本。
    • 不错的答案。请注意,使用 try-except 比不使用 try-except 的等效解决方案慢约 15%(当我在 CPython 2.7 上尝试时)。
    【解决方案2】:

    如果你想“公平”就不要。

    假设您有一个包含一百万个项目的列表,另一个仅包含两个项目。 “公平”随机化将短列表中的第一个元素出现在大约索引 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,...
        ^   ^
    

    【讨论】:

    • 与最初的问题一样,我不需要随机化是公平的。我会对“不公平”的随机化感到满意。
    • 谢谢马克,我很欣赏答案的公平性是一个重要的考虑因素,如果一个人在现实世界的场景中这样做。然而,在这种情况下,我只想要一个随机的解决方案,因此短列表中的项目有可能(而且确实必须有可能)出现在结果列表中的任何位置。
    • @srgerg:这两种方法都可能,而不是来自 b 的项目可以出现在列表中的任何位置。但是“公平”的方法使它可能不太可能使用您接受的方法从索引 100 之后的 b 获取元素,但我同意这在技术上并非不可能。
    • 啊,现在我很欣赏你所说的。好点!
    • 我在 my own answer 中添加了一些 cmets 到这个问题,展示了一个解决方案在不知道其输入长度的情况下有多“不公平”。再次感谢。
    【解决方案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 个元素的列表与单个元素列表(哨兵)交错。我重复了测试 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
    

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

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

    【讨论】:

    • +1:改组迭代器是个好主意!但是,我希望列表理解表达式更易于阅读。我还为您的代码添加了一个更直接(可能更快)的版本。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-09
    • 2012-09-30
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多