Conozco la función zip (que se comprimirá de acuerdo con la lista más corta) y zip_longest (que se comprimirá de acuerdo con la lista más larga), pero ¿cómo puedo comprimir de acuerdo con la primera lista, independientemente de si es la más larga o no?
Por ejemplo:
Input: ['a', 'b', 'c'], [1, 2] Output: [('a', 1), ('b', 2), ('c', None)]Pero también:
Input: ['a', 'b'], [1, 2, 3] Output: [('a', 1), ('b', 2)]¿Existen ambas funcionalidades en una función?
Aquí hay otra toma, si el objetivo es un código legible y fácil de entender:
def zip_first(first, *rest, fillvalue=None): rest = [iter(r) for r in rest] for x in first: yield x, *(next(r, fillvalue) for r in rest) Esto usa la forma de dos argumentos de next() para devolver el valor de relleno para todos los iterables que están agotados.
Para exactamente dos iterables, esto se puede simplificar a
def zip_first(first, second, fillvalue=None): second = iter(second) for x in first: yield x, next(second, fillvalue)Devuelve solo los elementos len(a) de zip_longest :
from itertools import zip_longest def zip_first(a, b): z = zip_longest(a, b) for i, r in zip(range(len(a)), z): yield rUn poco feo, pero me quedaría con este. La idea es acortar la segunda lista al tamaño de la primera si es más larga. Luego usamos zip_longest para garantizar que el resultado sea al menos tan largo como el primer argumento de zip .
import itertools input1 = [['a', 'b', 'c'], [1, 2]] input2 = [['a', 'b'], [1, 2, 3]] zip1 = itertools.zip_longest(input1[0], input1[1][:len(input1[0])]) zip2 = itertools.zip_longest(input2[0], input2[1][:len(input2[0])]) print(list(zip1)) print(list(zip2))Producción:
[('a', 1), ('b', 2), ('c', None)] [('a', 1), ('b', 2)]Para comprimir varias listas, esto se puede usar:
import itertools def zip_first(lists): equal_lists = [l[:len(lists[0])] for l in lists] return itertools.zip_longest(*equal_lists)No conozco uno readymade, pero puedes definir el tuyo propio.
El uso de object() como centinela garantiza que siempre se probará como único y nunca se confundirá con None o cualquier otro valor de relleno. Por lo tanto, esto debería comportarse correctamente incluso si alguno de sus iterables contiene None .
Al igual que zip_longest , toma cualquier número de iterables (no necesariamente dos) y puede especificar el valor de fillvalue .
from itertools import zip_longest def zip_left(*iterables, fillvalue=None): SENTINEL = object() for first, *others in zip_longest(*iterables, fillvalue=SENTINEL): if first is SENTINEL: return others = [i if i is not SENTINEL else fillvalue for i in others] yield (first, *others) print(list(zip_left(['a', 'b', 'c'], [1, 2]))) print(list(zip_left(['a', 'b'], [1, 2, 3])))Producción:
[('a', 1), ('b', 2), ('c', None)] [('a', 1), ('b', 2)]Para iteradores genéricos (o listas también), puede usar esto. Obtenemos pares hasta que llegamos a StopIteration en a . Si presionamos StopIteration en b primero, usamos None como el segundo valor.
def zip_first(a, b): ai, bi = iter(a), iter(b) while True: try: aa = next(ai) except StopIteration: return try: bb = next(bi) except StopIteration: bb = None yield aa, bbPuede reutilizar el código python "más o menos equivalente" que se muestra en los documentos para itertools.zip_longest para hacer una versión generalizada que se comprima según la longitud del primer argumento:
from itertools import repeat def zip_by_first(*args, fillvalue=None): # zip_by_first('ABCD', 'xy', fillvalue='-') --> Ax By C- D- # zip_by_first('ABC', 'xyzw', fillvalue='-') --> Ax By Cz if not args: return iterators = [iter(it) for it in args] while True: values = [] for i, it in enumerate(iterators): try: value = next(it) except StopIteration: if i == 0: return iterators[i] = repeat(fillvalue) value = fillvalue values.append(value) yield tuple(values) Es posible que pueda realizar algunas pequeñas mejoras, como almacenar en caché repeat(fillvalue) o menos. El problema con esta implementación es que está escrita en Python, mientras que la mayoría de itertools usa una implementación C mucho más rápida. Puede ver los efectos de esto comparándolo con la respuesta de Kelly Bundy .
Si las entradas son listas (u otras colecciones que se pueden usar con len ), puede usar zip_longest y limitar perezosamente el resultado a la longitud de la primera lista 1 , usando islice :
from itertools import islice, zip_longest def zip_first(a, b): return islice(zip_longest(a, b), len(a))1 Esta idea básica fue tomada de la respuesta de Jan Christoph Terasa .
no se pero
first = ['a', 'b', 'c'] last = [1, 2, 3, 4] if len(first) < len(last): b = list(zip(first, last)) else: b = list(zip_longest(first, last)) print(b)Haga que el segundo sea infinito y luego use el zip normal:
from itertools import chain, repeat a = ['a', 'b', 'c'] b = [1, 2] b = chain(b, repeat(None)) print(*zip(a, b))