【问题标题】:Is there a middle ground between `zip` and `zip_longest``zip` 和 `zip_longest` 之间是否存在中间地带
【发布时间】:2012-11-12 09:33:08
【问题描述】:

假设我有这三个列表:

a = [1, 2, 3, 4]
b = [5, 6, 7, 8, 9]
c = [10, 11, 12]

是否有这样的内置函数:

somezip(a, b) == [(1, 5), (2, 6), (3, 7), (4, 8)]
somezip(a, c) == [(1, 10), (2, 11), (3, 12), (4, None)]

行为介于zipzip_longest 之间?

【问题讨论】:

  • 换句话说,一个总是取第一个参数长度的zip?
  • @jpm:正确。我想这将是一个更好的解释方式

标签: python


【解决方案1】:

没有,但您可以轻松组合takewhileizip_longest 的功能来实现您想要的功能

from itertools import takewhile, izip_longest
from operator import itemgetter
somezip = lambda *p: list(takewhile(itemgetter(0),izip_longest(*p)))

(如果第一个迭代器可能有计算结果为 False 的项目,您可以将 itemgetter 替换为 lambda 表达式 - 请参阅 @ovgolovin 的评论)

somezip = lambda *p: list(takewhile(lambda e: not e[0] is None,izip_longest(*p)))

例子

>>> from itertools import takewhile, izip_longest
>>> from operator import itemgetter
>>> a = [1, 2, 3, 4]
>>> b = [5, 6, 7, 8, 9]
>>> c = [10, 11, 12]
>>> somezip(a,b)
[(1, 5), (2, 6), (3, 7), (4, 8)]
>>> somezip(a,c)
[(1, 10), (2, 11), (3, 12), (4, None)]
>>> somezip(b,c)
[(5, 10), (6, 11), (7, 12), (8, None), (9, None)]

【讨论】:

  • 如果第一个迭代器的元素评估为False
  • 如果第一个迭代器有None 元素怎么办? :)
  • 在我看来是安全的,这个解决方案需要为填充创建临时唯一元素。因为如果我们使用None,第一个列表也可以包含None,我们无法区分izip_longes返回的None和预包含在迭代器中的None
  • 一个小的一般性损失是填充值必须是None
【解决方案2】:
import itertools as it

somezip = lambda *x: it.islice(it.izip_longest(*x), len(x[0]))



>>> list(somezip(a,b))
[(1, 5), (2, 6), (3, 7), (4, 8)]

>>> list(somezip(a,c))
[(1, 10), (2, 11), (3, 12), (4, None)]

【讨论】:

  • 那不是 first 调用 zip_longestthen 削减结果吗?如果x 之一是无限迭代器怎么办?
  • @JoachimSauer - 不。 list(it.islice(it.count(), 10)) 返回一个短名单。
  • 哦,我不知道,太棒了!
  • 如果第一个迭代器不是列表,len 将额外通过来确定长度。此外,它将消耗迭代器。 (在问题中,OP 声明输入是列表。我只是发表评论以澄清这一点,以便其他可能发现这个问题与他们的任务相关的人)。
  • @ovgolovin:我不明白。如果x[0] 是一个生成器,比方说,而不是通过额外的传递来确定长度,你不会得到TypeError: object of type 'generator' has no len()吗?
【解决方案3】:

您的输出似乎受到第一个迭代器it1 的输出的限制。所以我们可以按原样使用it1,并用无限的None-yielding 迭代器和zip 填充it2

>>> from itertools import repeat,izip,chain
>>> somezip = lambda it1,it2: izip(it1,chain(it2,repeat(None)))

>>> list(somezip(a,b))
[(1, 5), (2, 6), (3, 7), (4, 8)]
>>> list(somezip(a,c))
[(1, 10), (2, 11), (3, 12), (4, None)]

repeat(None) 创建迭代器,无限生成None

chain 粘合 it2repeat(None)

izip 将在it1 用完后立即停止生成。

其他解决方案有一些缺陷(我在 cmets 中留下了评论)。它们可能运行良好,但有一些输入可能会意外失败。


正如 glglgl 在 cmets 中建议的那样,此函数宁愿接受参数中可变数量的迭代器。

所以我更新了代码来工作:

from itertools import repeat,izip,chain,imap
somezip = lambda it1,*its: izip(it1,*imap(chain,its,repeat(repeat(None))))

测试:

>>> print(list(somezip(a,b)))
    print(list(somezip(a,c)))
    print(list(somezip(b,a,c)))

[(1, 5), (2, 6), (3, 7), (4, 8)]
[(1, 10), (2, 11), (3, 12), (4, None)]
[(5, 1, 10), (6, 2, 11), (7, 3, 12), (8, 4, None), (9, None, None)]

我不得不在这里使用imap,尽管没有必要这样做(因为参数稍后会被解包,所以简单的map 就可以了)。原因是mapdon't accept iterators of different length,而imap在消耗最小迭代器时停止。

所以,imapchain 应用于除第一个迭代器和chains 之外的所有迭代器,每个迭代器都使用repeat(None)。为了服务its 的每个迭代器,我在repeat(None) 上方使用了另一个repeat(注意,这在其他项目中可能非常危险,因为外部repeat 产生的所有对象都是同一个对象repeat(None),所以最后他们所有的chained 迭代器共享它)。然后我解压缩imap 对象以生成izip 的参数,它返回值直到it1 被消耗(因为chained its 现在每个都生成无限的值序列)。

请注意,所有操作都在纯 C 中工作,因此不涉及解释器开销。

为了阐明它的工作原理,我添加了以下详细说明:

def somezip(it1,*its): #from 0 to infinite iterators its
    # it1 -> a1,a2,a3,...,an
    # its -> (b1,b2,b3,...,bn),(c1,c2,c3,...,cn),...
    infinite_None = repeat(None) # None,None,None,...
    infinite_Nones = repeat(infinite_None) # infinite_None,infinite_None,... (share the same infinite_None)
    chained = imap(chain,its,infinite_Nones) # [(b1,b2,b3,...,bn,None,None,...),(c1,c2,c3,...,cn,None,None,...),...]
    return izip(it1,*chained)

单线就是:

somezip = lambda it1,*its: izip(it1,*imap(chain,its,repeat(repeat(None))))

【讨论】:

  • +1。最好的之一,因为它不需要调用len(it1)。但也必须有一种方法来支持任意数量的迭代......
  • @glglgl 谢谢!我以为只有 2 个迭代器。我将为任意数量的迭代器添加代码。
  • 在问题中,只有 2 个。但如果我们可以使其通用,这将是 IMO 的一个好处,因为 zip()izip()izip_longest() 都接受超过 2 个。 ..
  • @glglgl 我已经添加了可变参数版本!
【解决方案4】:

定义你自己的函数:

In [64]: def myzip(*args):
    lenn=len(args[0])
    return list(izip_longest(*[islice(x,lenn) for x in args],fillvalue=None))
   ....: 

In [30]: myzip(a,b)
Out[30]: [(1, 5), (2, 6), (3, 7), (4, 8)]

In [31]: myzip(b,c)
Out[31]: [(5, 10), (6, 11), (7, 12), (8, None), (9, None)]

In [32]: myzip(a,c)
Out[32]: [(1, 10), (2, 11), (3, 12), (4, None)]

【讨论】:

    【解决方案5】:

    这比其他的要长,但相对容易理解,如果这很重要的话。 ;-)

    a = [1, 2, 3, 4]
    b = [5, 6, 7, 8, 9]
    c = [10, 11, 12]
    def g(n): return xrange(n)  # simple generator
    
    def my_iter(iterable, fillvalue=None):
        for i in iterable: yield i
        while True: yield fillvalue
    
    def somezip(*iterables, **kwds):
        fillvalue = kwds.get('fillvalue')
        iters = [my_iter(i, fillvalue) for i in iterables]
        return [tuple(next(it) for it in iters) for i in iterables[0]]
    
    print 'somezip(a, b):', somezip(a, b)
    print 'somezip(a, c):', somezip(a, c)
    print 'somezip(a, g(2)):', somezip(a, g(2))
    print 'somezip(g(2), a):', somezip(g(2),a)
    print 'somezip(a, b, c):', somezip(a, b, c)
    print 'somezip(a, b, c, g(2)):', somezip(a, b, c, g(2))
    print 'somezip(g(2), a, b, c):', somezip(g(2), a, b, c)
    

    输出:

    somezip(a, b): [(1, 5), (2, 6), (3, 7), (4, 8)]
    somezip(a, c): [(1, 10), (2, 11), (3, 12), (4, None)]
    somezip(a, g(2)): [(1, 0), (2, 1), (3, None), (4, None)]
    somezip(g(2), a): [(1, 1)]
    somezip(a, b, c): [(1, 5, 10), (2, 6, 11), (3, 7, 12), (4, 8, None)]
    somezip(a, b, c, g(2)): [(1, 5, 10, 0), (2, 6, 11, 1), (3, 7, 12, None), (4, 8, None, None)]
    somezip(g(2), a, b, c): [(1, 1, 5, 10)]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-03
      • 2016-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-02
      • 2021-09-09
      相关资源
      最近更新 更多