在`zip`和`zip_longest`之间是否有一个折中的方法?

7

假设我有以下三个列表:

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之间表现如何?


11
换句话说,一个始终采用第一个参数长度的压缩文件? - jpm
@jpm: 没错。我想这将是更好的解释方式。 - Eric
5个回答

9

没有现成的方法,但你可以轻松地结合takewhileizip_longest的功能来实现你想要的效果。

from itertools import takewhile, izip_longest
from operator import itemgetter
somezip = lambda *p: list(takewhile(itemgetter(0),izip_longest(*p)))
< p>(如果第一个迭代器可能具有评估为False的项,则可以使用lambda表达式替换itemgetter-请参阅@ovgolovin的评论)< /p> (如果第一个迭代器可能包含结果为假的项目,则可以将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”会怎样? - ovgolovin
1
如果第一个迭代器有“None”元素怎么办? :) - ovgolovin
在我看来,这个解决方案似乎是安全的,但需要创建一个临时的唯一元素来进行填充。因为如果我们使用 None,第一个列表也可能包含 None,我们无法区分由 izip_longes 返回的 None 和迭代器中预先包含的 None - ovgolovin
一个小的限制是fillvalue必须为None - martineau

5
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)]

1
这不是先调用zip_longest,然后再截取结果吗?如果x中有一个是无限迭代器会怎样? - Joachim Sauer
1
@JoachimSauer - 不会。list(it.islice(it.count(), 10)) 返回一个短列表。 - eumiro
哦,我不知道,很棒! - Joachim Sauer
1
如果第一个迭代器不是列表,len 将需要额外的遍历以确定长度。此外,它将消耗迭代器。(在问题中,OP 表示输入是列表。我只是为了澄清这一点,让其他人可以将此问题用于他们的任务)。 - ovgolovin
1
@ovgolovin:我不明白。如果x [0]是一个生成器,那么除了获取长度之外,您不会得到TypeError:object of type 'generator' has no len()吗? - DSM
显示剩余2条评论

3
你的输出似乎受到了第一个迭代器 it1 的限制。因此,我们可以直接使用 it1,并用无限产生 None 的迭代器填充 it2,然后将它们进行 zip 操作。
>>> 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

chainit2repeat(None)粘合在一起。

izip会在it1耗尽时立即停止产生。

其他解决方案存在一些缺陷(我已在注释中留下了备注)。它们可能运行良好,但对于某些输入,它们可能会出现意外失败。


如评论中glglgl所建议的,这个函数应该接受可变数量的迭代器参数。
因此,我更新了代码以实现此功能:
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)]

尽管没有必要这样做(因为参数稍后会被解包,所以普通的map就可以),但我在这里必须使用imap。原因是map不接受长度不同的迭代器,而imap会在最小的迭代器被消耗完时停止。

所以,imapchain应用于除第一个迭代器之外的所有迭代器,并使用repeat(None)将每个迭代器chain在一起。为了服务于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
@glglgl 谢谢!我原以为只有两个迭代器。我会添加代码以支持任意数量的迭代器。 - ovgolovin
在这个问题中,只有两个。但是如果我们可以将其通用化,我认为这将是一个好处,因为zip()izip()izip_longest()都接受超过2个参数... - glglgl
@glglgl,我已经添加了可变参数版本! - ovgolovin

0

定义你自己的函数:

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)]

0

这段文字比其他的长,但如果有关系的话,相对容易理解。 ;-)

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)]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接