Есть ли золотая середина между `zip` и` zip_longest`

Скажем, у меня есть эти три списка:

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

Ведя себя где-то междуzip а также ?zip_longest

 jpm12 нояб. 2012 г., 10:36
Другими словами, почтовый индекс, который всегда принимает длину первого аргумента?
 Eric12 нояб. 2012 г., 10:46
@jpm: правильно. Я думаю, что это будет лучший способ объяснить это

Ответы на вопрос(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)]
 glglgl12 нояб. 2012 г., 13:15
ITYMwhile True: yield fillvalue...
 martineau12 нояб. 2012 г., 16:56
@glglgl: Да ... хороший улов.

Кажется, ваш вывод ограничен выводом первого итератораit1, Таким образом, мы могли бы использоватьit1 как есть и колодкиit2 с бесконечнымNoneитератор и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 бесконечно.

chain клеиit2 а также .repeat(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)]

Я должен был использоватьimap здесь, хотя в этом не было необходимости (поскольку параметр позже распаковывается,map сделал бы). Причина была в том, чтоmap дон»принимать итераторы разной длины, в то время какimap остановка, пока используется самый маленький итератор.

Так,imap относитсяchain всем итераторам, кроме первого иchainс каждым из нихrepeat(None), Чтобы служить каждому итераторуits Я использовал другойrepeat вышеrepeat(None) (обратите внимание, это может быть очень опасно в других проектах, так как все объекты, которые внешниеrepeat производит один и тот же объектrepeat(None)так что, в конце концов, они всеchainитераторы поделились этим). Тогда я распаковалimap объект для получения параметровizip, который возвращает значения доit1 потребляется (какchainиздание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))))
 ovgolovin12 нояб. 2012 г., 17:12
@glglgl Спасибо! Я думал, что есть только 2 итератора. Я'добавлю код для произвольного числа итераторов.
 glglgl12 нояб. 2012 г., 17:16
В вопросе есть только 2. Но если мы сможем сделать его общим, это будет выгодно IMO, так какzip() а такжеizip() а такжеizip_longest() все принимают более 2-х ...
 glglgl12 нояб. 2012 г., 17:07
+1. Один из лучших, потому что это нене нужно звонитьlen(it1), Но должен быть способ поддерживать произвольное количество повторяемых элементов ...
 ovgolovin12 нояб. 2012 г., 17:52
@glglgl Я добавил вариационную версию!

определите свою собственную функцию:

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)]
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)]
 ovgolovin12 нояб. 2012 г., 11:07
Если первый итератор не является списком,len займет дополнительный проход, чтобы определить длину. Кроме того, он будет использовать итератор. (В вопросе OP говорится, что входные данные являются списками. Я просто комментирую, чтобы прояснить этот вопрос для других, которые могут найти этот вопрос соответствующим их задачам).
 eumiro12 нояб. 2012 г., 10:42
@JoachimSauer - нет.list(it.islice(it.count(), 10)) возвращает короткий список
 Joachim Sauer12 нояб. 2012 г., 10:40
Безразлично»т этопервый вызовzip_longest а такжезатем сократить результат? Что делать, если один изx такое бесконечный итератор?
 poke12 нояб. 2012 г., 10:52
Тот'потому чтоislice генерирует значения на лету из аргумента, который в этом случае (izip_longest) тоже генератор.
 Joachim Sauer12 нояб. 2012 г., 10:44
о, я нене знаю этого, аккуратно!
 DSM12 нояб. 2012 г., 13:22
@ovgolovin: я неТ понять. Еслиx[0] скажем, генератор, вместо того, чтобы сделать дополнительный проход, чтобы определить длину, выигралты понял?TypeError: object of type 'generator' has no len()
 ovgolovin12 нояб. 2012 г., 13:57
@DSM Вы правы. Я ошибочно думал чтоlen будет использовать итератор, чтобы вернуть его длину. Теперь я проверил и получил то же исключение, которое вы цитировали.
Решение Вопроса

Нет нетт, но вы можете легко объединить функциональностьTakeWhile а такжеizip_longest добиться того, что вы хотите

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

(В случае, если первый итератор может иметь элементы, которые оцениваются как False, вы можете заменить элементный виджет лямбда-выражением - см. @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)]
 ovgolovin12 нояб. 2012 г., 11:09
Что если в первом итераторе есть элементы для оценки?False
 Abhijit12 нояб. 2012 г., 11:13
@ovgolovin: Да, имеет смысл. Я обновил свой ответ
 ovgolovin12 нояб. 2012 г., 11:17
Мне кажется безопасным, это решение потребует создания временного уникального элемента для заполнения. Потому что, если мы используемNoneПервый список также может содержатьNoneи мы можемт разграничитьNone вернулсяizip_longes иNone, содержащийся в итераторе.
 martineau12 нояб. 2012 г., 18:01
Одна небольшая потеря общности заключается в том, что значение заполнения должно быть.None
 ovgolovin12 нояб. 2012 г., 11:14
Что делать, если первый итераторNone элементы? :)

Ваш ответ на вопрос