두 목록을 번갈아 가며 결합하는 Pythonic 방법?


90

두 개의 목록 이 있는데 첫 번째 목록은 두 번째 목록 보다 정확히 하나 이상의 항목을 포함합니다 . 첫 번째 목록에서 짝수 인덱스 값을 가져오고 두 번째 목록에서 홀수 인덱스 값을 가져 오는 새 목록을 만드는 가장 파이썬적인 방법을 알고 싶습니다.

# example inputs
list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']

# desired output
['f', 'hello', 'o', 'world', 'o']

이것은 작동하지만 예쁘지 않습니다.

list3 = []
while True:
    try:
        list3.append(list1.pop(0))
        list3.append(list2.pop(0))
    except IndexError:
        break

어떻게 이것을 달성 할 수 있습니까? 가장 Pythonic 접근 방식은 무엇입니까?



중복이 아닙니다! 위에 링크 된 기사에서 허용 된 답변은 단일 병합 목록이 아닌 튜플 목록을 생성합니다.
Paul Sasik

@Paul : 예, 수락 된 대답은 완전한 솔루션을 제공하지 않습니다. 의견 및 기타 답변을 읽으십시오. 질문은 기본적으로 동일하며 여기에 다른 솔루션을 적용 할 수 있습니다.
Felix Kling

3
@Felix : 정중하게 동의하지 않습니다. 사실, 질문이 같은 이웃에 있지만 실제로 중복되지는 않습니다. 모호한 증거로 여기에서 잠재적 인 답변을 살펴보고 다른 질문과 비교하십시오.
Paul Sasik

답변:


113

슬라이싱으로 수행하는 한 가지 방법은 다음과 같습니다.

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

3
고마워, 던컨. 슬라이싱 할 때 단계를 지정할 수 있다는 사실을 몰랐습니다. 이 접근 방식에 대해 내가 좋아하는 것은 그것이 얼마나 자연스럽게 읽는가입니다. 1. 올바른 길이의 목록을 만드십시오. 2. list1의 내용으로 짝수 인덱스를 채웠습니다. 3. list2의 내용으로 홀수 인덱스를 채 웁니다. 이 경우 목록의 길이가 다르다는 사실은 문제가되지 않습니다!
davidchambers

2
len (list1)-len (list2)가 0 또는 1
xan

1
목록의 길이가 적절한 경우 작동하지만 그렇지 않은 경우 원래 질문은 예상되는 답변을 지정하지 않습니다. 대부분의 합리적인 상황을 처리하도록 쉽게 수정할 수 있습니다. 예를 들어 추가 요소를 무시하려면 시작하기 전에 긴 목록을 잘라내십시오. 추가 요소를 None으로 인터리브하려면 결과가 더 많은 None으로 초기화되었는지 확인하십시오. 마지막에 추가 요소를 추가하려면 무시하고 추가하십시오.
Duncan

1
나도 분명하지 않았다. 내가 말하려는 요점은 나열된 많은 것들과 달리 Duncan의 솔루션이 목록의 길이가 같지 않다는 사실로 인해 복잡하지 않다는 것입니다. 물론 제한된 범위의 상황에서만 적용 할 수 있지만 두 목록에서 작동하는 덜 우아한 솔루션보다이 인스턴스에서 작동하는 정말 우아한 솔루션을 선호합니다.
davidchambers

1
(len (list1) + len (list2)) 대신 (2 * len (list1) -1)을 사용할 수 있습니다. 또한 [:: 2] 대신 [0 :: 2]를 선호합니다.
Lord British

50

itertools문서 에 이에 대한 레시피가 있습니다 .

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

편집하다:

파이썬 버전이 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))

이 방법이 필요 이상으로 복잡하다는 것을 알게되었습니다. 를 사용하는 아래에 더 나은 옵션이 있습니다 zip_longest.
Dubslow 2011

@Dubslow이 특별한 경우에, 예, 이것은 당신이 이미 그것에 접근 할 수있는 경우가 아니라면 아마도 과잉 일 것입니다 (다른 곳에서 언급했듯이). 하지만 다른 상황에서는 몇 가지 장점이있을 수 있습니다. 이 레시피는 확실히이 문제를 해결하기 위해 고안된 것이 아닙니다.
David Z

1
참고로 더 이상 작동하지 않기 때문에 itertools 설명서 의 레시피를 사용해야 .next()합니다.
존 W.

1
@johnw. 하나를 사용해야 __next__합니다. 문서에 작성되지 않았으므로 답변에 대한 편집을 제안했습니다.
Marine Galantin

@Marine 그냥 기존 코드 샘플을 변경 한 것 같지만 직접 수정할 수 있습니다. 기여해 주셔서 감사합니다!
David Z

30

이것은 당신이 원하는 것을해야합니다 :

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']

나는 당신의 초기 답변을 정말 좋아했습니다. 질문을 완벽하게 해결하지는 못했지만 동일한 길이의 두 목록을 병합하는 우아한 방법이었습니다. 현재 답변에서 길이 경고와 함께 유지하는 것이 좋습니다.
Paul Sasik

1
list1이 대신 [ 'f', 'o', 'o', 'd']이면 최종 항목 ( 'd')이 결과 목록에 나타나지 않습니다 (질문의 세부 사항을 고려할 때 완전히 괜찮습니다). 이것은 우아한 솔루션입니다!
davidchambers

1
@Mark yep (나는 그것을 upvote했다), 단지 차이점을 지적하고 (다른 사람들이 다른 행동을 원하는 경우 제한 사항)
cobbal

4
명시된 문제를 해결하고 간단히 해결하기 위해 +1 :-) 나는 이와 같은 일이 가능할 것이라고 생각했습니다. 솔직히이 roundrobin기능은이 상황에서 일종의 과잉 이라고 생각합니다 .
David Z

1
모든 크기의 목록으로 작업하려면 반복자에 남은 것을 결과에 추가하면됩니다.list(itertools.chain(map(next, itertools.cycle(iters)), *iters))
panda-34

29
import itertools
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]

저는 이것이 가장 비단뱀적인 방법이라고 생각합니다.


3
왜 이것이 허용되지 않는 대답입니까? 이것은 가장 짧고 가장 비단뱀이며 다른 목록 길이로 작동합니다!
Jairo Vadillo

5
메소드 이름은하지 izip_longest zip_longest입니다
하이로 Vadillo

1
이 문제는 zip_longest의 기본 채우기 값 None이 목록에있는 것으로 추정되는을 덮어 쓸 수 있다는 것입니다. 이 문제를 수정하기 위해 수정 된 버전으로 편집하겠습니다
Dubslow

참고 : 목록 값을 가진 요소가 포함 된 경우이 문제가 발생할 것입니다 False, 또는 유일한 것조차 가지 평가False바이 if예를 들어 A에 대한 같은 -expression 0또는 빈 목록을. 이것은 (부분적으로) 다음과 같은 방법으로 피할 수 있습니다 : [x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]. 물론, 목록에 None보존해야하는 요소가 포함되어 있으면 여전히 작동하지 않습니다 . 이 경우 Dubslow가 이미 제안한대로 의 fillvalue인수 를 변경해야합니다 zip_longest.
der_herr_g

None문제는 적어도 Python 3.7.6 이후로 사라진 것 같습니다 (이전 버전은 모르겠습니다). 가로 alt_chain정의 def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue))되면을 list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))올바르게 반환합니다 [0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99].
paime

18

itertools없이 l1이 l2보다 1 개의 항목이 더 길다고 가정합니다.

>>> sum(zip(l1, l2+[0]), ())[:-1]
('f', 'hello', 'o', 'world', 'o')

itertools를 사용하고 목록에 None이 포함되어 있지 않다고 가정합니다.

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')

이것이 제가 가장 좋아하는 대답입니다. 너무 간결합니다.
mbomb007

@ anishtain4 zip은 목록에서 요소 쌍을 튜플로 가져옵니다 [(l1[0], l2[0]), (l1[1], l2[1]), ...]. sum튜플을 함께 연결 (l1[0], l2[0]) + (l1[1], l2[1]) + ...하여 인터리브 목록을 생성합니다. 나머지 한 줄은 zip이 작동하도록 추가 요소로 l1을 채우고 해당 패딩을 제거하기 위해 -1까지 슬라이스하는 것입니다.
Zart

izip_longest (python 3 이후 zip_longest)는 + [0] 패딩이 필요하지 않습니다. 목록 길이가 일치하지 않을 때 암시 적으로 None을 채우고 filter(None, ...( bool대신 사용할 수 있거나 또는 None.__ne__) 0, None 및 빈 문자열을 포함한 거짓 값을 제거합니다. 두 번째 표현은 첫 번째 표현과 완전히 동일하지 않습니다.
Zart

문제는 어떻게 만들었 sum습니까? 두 번째 주장의 역할은 무엇입니까? 문서에서 두 번째 인수는 start입니다.
anishtain4

start의 기본값은 0이고 0+ (some, tuple)을 할 수 없으므로 start는 empty tuple로 변경됩니다.
Zart

13

나는 질문이 하나의 항목이 다른 항목보다 더 많은 두 목록에 대해 묻는 것을 알고 있지만이 질문을 찾을 수있는 다른 사람들을 위해 이것을 넣을 것이라고 생각했습니다.

다음은 서로 다른 크기의 두 목록을 사용하도록 조정 된 Duncan의 솔루션 입니다.

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

결과는 다음과 같습니다.

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 

6

두 목록의 길이가 같으면 다음을 수행 할 수 있습니다.

[x for y in zip(list1, list2) for x in y]

첫 번째 목록에는 요소가 하나 더 있으므로 사후에 추가 할 수 있습니다.

[x for y in zip(list1, list2) for x in y] + [list1[-1]]

2
^ 이것이 답이 될 것입니다. 파이썬은 지난 10 년 동안 더욱 비단뱀이되었습니다
Tian

5

다음은이를 수행하는 하나의 라이너입니다.

list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]


2
이것은 올바르게 작동하지만 너무 간단한 것을 달성하기 위해 너무 많은 일을하기 때문에 나를 우아하지 않게 생각합니다. 나는이 접근 방식이 비효율적이라고 말하는 것이 아니라 단순히 읽기가 특별히 쉽지 않다는 것입니다.
davidchambers

2
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

변경 가능한 기본 인수를 사용할 때 매우주의하십시오. 이후 모든 호출에 대해 lst가 재사용되므로 처음 호출 할 때만 정답을 반환합니다. lst = None ... if lst가 None : lst = []로 작성하는 것이 더 좋을 것입니다. 비록 여기에 나열된 다른 접근 방식보다이 접근 방식을 선택해야하는 설득력있는 이유는 보이지 않지만.
davidchambers

lst는 함수 내에서 정의되므로 지역 변수입니다. 잠재적 인 문제는 다른 목록으로 함수를 호출하더라도 함수를 사용할 때마다 list1과 list2가 재사용된다는 것입니다. 참조 docs.python.org/tutorial/...
blokeley

1
@blokeley : 잘못되었습니다. Combine (list1 = [...], list2 = [...])이면 재사용 될 것입니다.
killown

이 솔루션이 처음 게시되었을 때 첫 번째 줄은 읽었 def combine(list1, list2, lst=[]):으므로 내 의견입니다. 하지만 내가 그 의견을 제출할 때까지 killown은 필요한 변경을했습니다.
davidchambers

2

이것은 여러 항목의 그룹을 대체하고 모든 항목이 출력에 있는지 확인하는 옵션과 함께 위의 Carlos Valiente의 기여를 기반으로합니다.

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

이렇게하면 목록 A와 B가 각각 3 개의 값 그룹별로 인터레이스됩니다.

['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]

2

조금 늦게 구매할 수도 있습니다. 이것은 두 목록의 크기가 같거나 같지 않을 때 작동합니다. 가치가없는 한 가지는 a와 b를 수정한다는 것입니다. 문제가되는 경우 다른 솔루션을 사용해야합니다.

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

1

내 테이크 :

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

1

다음은 다른 라이브러리없이 목록 이해를 사용하는 한 줄입니다.

list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]]

부작용으로 초기 목록 1의 변경을 허용하는 경우 다른 방법이 있습니다.

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]

1
from itertools import chain
list(chain(*zip('abc', 'def')))  # Note: this only works for lists of equal length
['a', 'd', 'b', 'e', 'c', 'f']

0

최단 시간에 중지 :

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

물론, next / __ next__ 메서드를 해결하는 것이 더 빠를 수 있습니다.


0

이것은 불쾌하지만 목록의 크기에 관계없이 작동합니다.

list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]

0

다른 질문대한 답변에서 영감을 얻은 여러 한 줄 :

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]

0

numpy는 어때? 문자열에서도 작동합니다.

import numpy as np

np.array([[a,b] for a,b in zip([1,2,3],[2,3,4,5,6])]).ravel()

결과:

array([1, 2, 2, 3, 3, 4])

0

기능적이고 변경 불가능한 방식의 대안 (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),[])

-1

나는 간단합니다.

chain.from_iterable( izip( list1, list2 ) )

추가 스토리지 요구 사항을 생성하지 않고 반복기가 제공됩니다.


1
정말 간단하지만 길이가 같은 목록에서만 작동합니다!
Jochen Ritzel

chain.from_iterable(izip(list1, list2), list1[len(list2):])여기에서 요청한 특정 문제에 대해 수정할 수 있습니다 . list1은 더 긴 것이어야합니다.
Jochen Ritzel

예, 그러나 임의의 길이 컨테이너에 대해 작동하거나 위에서 제안한 솔루션에 양보하는 솔루션을 생각해 내고 싶습니다.
wheaties

-2

나는 목록 이해력이 부족하기에는 너무 늙었 기 때문에 :

import operator
list3 = reduce(operator.add, zip(list1, list2))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.