Mam dwie listy, z których pierwsza na pewno zawiera dokładnie jedną pozycję więcej niż druga . Chciałbym poznać najbardziej Pythonowy sposób tworzenia nowej listy, której parzyste wartości indeksu pochodzą z pierwszej listy, a nieparzyste wartości z drugiej listy.
# example inputs
list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']
# desired output
['f', 'hello', 'o', 'world', 'o']
To działa, ale nie jest ładne:
list3 = []
while True:
try:
list3.append(list1.pop(0))
list3.append(list2.pop(0))
except IndexError:
break
Jak inaczej można to osiągnąć? Jakie jest najbardziej Pythonic podejście?
Odpowiedzi:
Oto jeden sposób na zrobienie tego przez cięcie:
>>> list1 = ['f', 'o', 'o'] >>> list2 = ['hello', 'world'] >>> result = [None]*(len(list1)+len(list2)) >>> result[::2] = list1 >>> result[1::2] = list2 >>> result ['f', 'hello', 'o', 'world', 'o']
źródło
Jest to przepis na to w
itertools
dokumentacji :from itertools import cycle, islice def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" # Recipe credited to George Sakkis pending = len(iterables) nexts = cycle(iter(it).next for it in iterables) while pending: try: for next in nexts: yield next() except StopIteration: pending -= 1 nexts = cycle(islice(nexts, pending))
EDYTOWAĆ:
Dla wersji Pythona większej niż 3:
from itertools import cycle, islice def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" # Recipe credited to George Sakkis pending = len(iterables) nexts = cycle(iter(it).__next__ for it in iterables) while pending: try: for next in nexts: yield next() except StopIteration: pending -= 1 nexts = cycle(islice(nexts, pending))
źródło
zip_longest
.itertools
dokumentacji, ponieważ.next()
już nie działa.__next__
. Nie ma tego w dokumentacji, więc zaproponowałem edycję odpowiedzi.Powinno to zrobić, co chcesz:
>>> iters = [iter(list1), iter(list2)] >>> print list(it.next() for it in itertools.cycle(iters)) ['f', 'hello', 'o', 'world', 'o']
źródło
roundrobin
funkcja jest przesadą w tej sytuacji.list(itertools.chain(map(next, itertools.cycle(iters)), *iters))
import itertools print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]
Myślę, że to najbardziej pytoniczny sposób na zrobienie tego.
źródło
None
, które * powinny znajdować się na liście. Zmienię poprawioną wersję, aby to naprawićFalse
, a nawet rzeczy, które będą tylko oceniane jakoFalse
przezif
-expression, jak na przykład0
lub pustej listy. Może to być (częściowo) unika się za pomocą następujących:[x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]
. Oczywiście to nadal nie zadziała, jeśli listy zawierająNone
elementy, które należy zachować. W takim przypadku musisz zmienićfillvalue
argumentzip_longest
, jak już zasugerował Dubslow.None
Wydaje się, że problem zniknął, przynajmniej od czasu Pythona 3.7.6 (nie znam starszych wersji). Jeślialt_chain
jest zdefiniowane jakodef alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue))
, tolist(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))
poprawnie zwraca[0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99]
.Bez itertools i zakładając, że l1 jest o 1 element dłuższy niż l2:
>>> sum(zip(l1, l2+[0]), ())[:-1] ('f', 'hello', 'o', 'world', 'o')
Korzystanie z itertools i zakładanie, że listy nie zawierają None:
>>> filter(None, sum(itertools.izip_longest(l1, l2), ())) ('f', 'hello', 'o', 'world', 'o')
źródło
[(l1[0], l2[0]), (l1[1], l2[1]), ...]
.sum
łączy ze sobą krotki,(l1[0], l2[0]) + (l1[1], l2[1]) + ...
co daje przeplatane listy. Reszta jednej linii to tylko wypełnienie l1 dodatkowym elementem, aby zip działał i kroił do -1, aby pozbyć się tego wypełnienia.filter(None, ...
(można użyćbool
zamiast tego lubNone.__ne__
) usuwa fałszywe wartości, w tym 0, None i puste ciągi, więc drugie wyrażenie nie jest ściśle równoważne z pierwszym.sum
zrobiłeś? Jaka jest tam rola drugiego argumentu? W dokumentacjach drugim argumentem jeststart
.Wiem, że pytania dotyczą dwóch list, z których jedna ma o jeden element więcej niż druga, ale pomyślałem, że zadam to innym, którzy mogą znaleźć to pytanie.
Oto rozwiązanie firmy Duncan przystosowane do pracy z dwiema listami o różnych rozmiarach.
list1 = ['f', 'o', 'o', 'b', 'a', 'r'] list2 = ['hello', 'world'] num = min(len(list1), len(list2)) result = [None]*(num*2) result[::2] = list1[:num] result[1::2] = list2[:num] result.extend(list1[num:]) result.extend(list2[num:]) result
To daje:
['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r']
źródło
Jeśli obie listy mają taką samą długość, możesz:
[x for y in zip(list1, list2) for x in y]
Ponieważ pierwsza lista ma jeszcze jeden element, możesz dodać ją post hoc:
[x for y in zip(list1, list2) for x in y] + [list1[-1]]
źródło
Oto jedna linijka, która to robi:
list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]
źródło
def combine(list1, list2): lst = [] len1 = len(list1) len2 = len(list2) for index in range( max(len1, len2) ): if index+1 <= len1: lst += [list1[index]] if index+1 <= len2: lst += [list2[index]] return lst
źródło
def combine(list1, list2, lst=[]):
, stąd mój komentarz. Zanim jednak przedstawiłem ten komentarz, killown dokonało koniecznej zmiany.Ten jest oparty na powyższym wkładzie Carlosa Valiente z opcją zmiany grup wielu elementów i upewnienia się, że wszystkie elementy są obecne w wyniku:
A=["a","b","c","d"] B=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] def cyclemix(xs, ys, n=1): for p in range(0,int((len(ys)+len(xs))/n)): for g in range(0,min(len(ys),n)): yield ys[0] ys.append(ys.pop(0)) for g in range(0,min(len(xs),n)): yield xs[0] xs.append(xs.pop(0)) print [x for x in cyclemix(A, B, 3)]
Spowoduje to przeplatanie list A i B grupami po 3 wartości:
['a', 'b', 'c', 1, 2, 3, 'd', 'a', 'b', 4, 5, 6, 'c', 'd', 'a', 7, 8, 9, 'b', 'c', 'd', 10, 11, 12, 'a', 'b', 'c', 13, 14, 15]
źródło
Może być trochę spóźniony, kup jeszcze jedną linijkę Pythona. Działa to, gdy obie listy mają równy lub nierówny rozmiar. Jedna rzecz nic nie jest warta to, że zmodyfikuje a i b. Jeśli jest to problem, musisz użyć innych rozwiązań.
a = ['f', 'o', 'o'] b = ['hello', 'world'] sum([[a.pop(0), b.pop(0)] for i in range(min(len(a), len(b)))],[])+a+b ['f', 'hello', 'o', 'world', 'o']
źródło
Moje podanie:
a = "hlowrd" b = "el ol" def func(xs, ys): ys = iter(ys) for x in xs: yield x yield ys.next() print [x for x in func(a, b)]
źródło
Oto jedna linijka używająca wyrażeń listowych, bez innych bibliotek:
list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]]
Oto inne podejście, jeśli pozwolisz na zmianę swojej początkowej listy1 przez efekt uboczny:
[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]
źródło
from itertools import chain list(chain(*zip('abc', 'def'))) # Note: this only works for lists of equal length ['a', 'd', 'b', 'e', 'c', 'f']
źródło
Zatrzymuje się na najkrótszym:
def interlace(*iters, next = next) -> collections.Iterable: """ interlace(i1, i2, ..., in) -> ( i1-0, i2-0, ..., in-0, i1-1, i2-1, ..., in-1, . . . i1-n, i2-n, ..., in-n, ) """ return map(next, cycle([iter(x) for x in iters]))
Jasne, rozwiązanie metody next / __ next__ może być szybsze.
źródło
To jest nieprzyjemne, ale działa niezależnie od rozmiaru list:
list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]
źródło
Wiele linijek inspirowanych odpowiedziami na inne pytanie :
import itertools list(itertools.chain.from_iterable(itertools.izip_longest(list1, list2, fillvalue=object)))[:-1] [i for l in itertools.izip_longest(list1, list2, fillvalue=object) for i in l if i is not object] [item for sublist in map(None, list1, list2) for item in sublist][:-1]
źródło
Co powiesz na numpy? Działa również ze sznurkami:
import numpy as np np.array([[a,b] for a,b in zip([1,2,3],[2,3,4,5,6])]).ravel()
Wynik:
array([1, 2, 2, 3, 3, 4])
źródło
Alternatywa w funkcjonalny i niezmienny sposób (Python 3):
from itertools import zip_longest from functools import reduce reduce(lambda lst, zipped: [*lst, *zipped] if zipped[1] != None else [*lst, zipped[0]], zip_longest(list1, list2),[])
źródło
Zrobiłbym proste:
Pojawi się iterator bez tworzenia dodatkowych wymagań dotyczących pamięci.
źródło
chain.from_iterable(izip(list1, list2), list1[len(list2):])
za pomocą konkretnego problemu tutaj zadanego ... lista1 powinna być dłuższa.Jestem za stary, żeby nie radzić sobie ze zrozumieniem list, więc:
import operator list3 = reduce(operator.add, zip(list1, list2))
źródło