Pythonic cách kết hợp hai danh sách trong một kiểu xen kẽ?


88

Tôi có hai danh sách, danh sách đầu tiên được đảm bảo chứa chính xác một mục nhiều hơn danh sách thứ hai . Tôi muốn biết cách Pythonic nhất để tạo một danh sách mới có giá trị chỉ số chẵn đến từ danh sách đầu tiên và có giá trị chỉ mục lẻ đến từ danh sách thứ hai.

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

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

Điều này hoạt động, nhưng không đẹp:

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

Làm thế nào khác có thể đạt được điều này? Cách tiếp cận Pythonic nhất là gì?



Không phải là một bản sao! Câu trả lời được chấp nhận trong bài viết được liên kết ở trên tạo ra một danh sách các bộ giá trị, không phải một danh sách được hợp nhất.
Paul Sasik

@Paul: Có, câu trả lời được chấp nhận không đưa ra giải pháp hoàn chỉnh. Đọc các bình luận và các câu trả lời khác. Câu hỏi về cơ bản là giống nhau và các giải pháp khác có thể được áp dụng ở đây.
Felix Kling

3
@Felix: Tôi xin trân trọng không đồng ý. Đó là sự thật, những câu hỏi ở cùng một khu phố nhưng không thực sự trùng lặp. Như bằng chứng mơ hồ, hãy xem các câu trả lời tiềm năng ở đây và so sánh với câu hỏi khác.
Paul Sasik

Kiểm tra những điều này: stackoverflow.com/questions/7529376/…
wordsforthewise

Câu trả lời:


112

Đây là một cách để làm điều đó bằng cách cắt:

>>> 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
Cảm ơn, Duncan. Tôi không nhận ra rằng có thể chỉ định một bước khi cắt. Điều tôi thích về cách tiếp cận này là cách nó đọc tự nhiên. 1. Lập danh sách có độ dài chính xác. 2. Điền các chỉ mục chẵn với nội dung của danh sách1. 3. Điền các chỉ mục lẻ với nội dung của danh sách2. Thực tế là các danh sách có độ dài khác nhau không phải là vấn đề trong trường hợp này!
davidchambers

2
Tôi nghĩ rằng nó chỉ hoạt động khi len (list1) - len (list2) là 0 hoặc 1.
xan 09/09

1
Nếu danh sách có độ dài thích hợp thì nó hoạt động, nếu không thì câu hỏi ban đầu không chỉ rõ câu trả lời được mong đợi là gì. Nó có thể dễ dàng sửa đổi để xử lý hầu hết các tình huống hợp lý: ví dụ: nếu bạn muốn các phần tử phụ bị bỏ qua, chỉ cần cắt nhỏ danh sách dài hơn trước khi bắt đầu; nếu bạn muốn các phần tử bổ sung xen kẽ với Không thì chỉ cần đảm bảo rằng kết quả đó được khởi tạo bằng một số phần tử khác; nếu bạn muốn các phần tử bổ sung vừa được thêm vào cuối thì hãy làm như bỏ qua chúng và sau đó nối chúng.
Duncan

1
Tôi cũng không rõ. Điểm tôi đang cố gắng đưa ra là giải pháp của Duncan, không giống như nhiều giải pháp được liệt kê, không phức tạp bởi thực tế là các danh sách có độ dài không bằng nhau. Chắc chắn, nó chỉ áp dụng được trong một số trường hợp hạn chế, nhưng tôi muốn một giải pháp thực sự thanh lịch hoạt động trong trường hợp này hơn một giải pháp kém thanh lịch hơn hoạt động cho hai danh sách bất kỳ.
davidchambers

1
Bạn có thể sử dụng (2 * len (list1) -1) thay vì (len (list1) + len (list2)), tôi cũng thích [0 :: 2] thay vì [:: 2].
Lord British

50

Có một công thức cho điều này trong itertoolstài liệu :

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

BIÊN TẬP:

Đối với phiên bản của python lớn hơn 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))

Tôi thấy cách này phức tạp hơn mức cần thiết. Có một tùy chọn tốt hơn bên dưới bằng cách sử dụng zip_longest.
Dubslow

@Dubslow Đối với trường hợp cụ thể này, vâng, điều này có thể là quá mức cần thiết (như tôi đã đề cập trong một nhận xét ở nơi khác), trừ khi bạn tình cờ đã có quyền truy cập vào nó. Tuy nhiên, nó có thể có một số lợi thế trong các tình huống khác. Công thức này chắc chắn không được thiết kế cho vấn đề này, nó chỉ xảy ra để giải quyết nó.
David Z

1
fyi bạn nên sử dụng công thức trong itertools tài liệu.next()không còn hoạt động.
john w.

1
@johnw. người ta phải sử dụng __next__. Nó không được viết trong tài liệu nên tôi đã đề xuất chỉnh sửa câu trả lời.
Marine Galantin

@Marine Tôi thà rằng bạn vừa thay đổi mẫu mã hiện có, nhưng tôi có thể tự sửa lỗi đó. Cảm ơn vì đã đóng góp!
David Z

31

Điều này sẽ làm những gì bạn muốn:

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

Tôi thực sự thích câu trả lời ban đầu của bạn. Mặc dù nó không giải quyết được câu hỏi một cách hoàn hảo, nhưng đó là một cách hay để hợp nhất hai danh sách có cùng độ dài. Tôi khuyên bạn nên giữ lại nó, cùng với báo trước về chiều dài, trong câu trả lời hiện tại của bạn.
Paul Sasik

1
Nếu thay vào đó list1 là ['f', 'o', 'o', 'd'], mục cuối cùng của nó ('d') sẽ không xuất hiện trong danh sách kết quả (hoàn toàn ổn nếu đưa ra các chi tiết cụ thể của câu hỏi). Đây là một giải pháp thanh lịch!
davidchambers

1
@ Mark vâng (I did upvote nó), chỉ cần chỉ ra sự khác biệt (và hạn chế nếu người khác muốn hành vi khác nhau)
cobbal

4
+1 để giải quyết vấn đề đã nêu và để thực hiện nó một cách đơn giản nữa :-) Tôi đã tìm ra thứ như thế này sẽ khả thi. Thành thật mà nói, tôi nghĩ rằng roundrobinchức năng là loại quá mức cần thiết cho tình huống này.
David Z

1
Để làm việc với các danh sách có kích thước bất kỳ, bạn có thể chỉ cần nối những gì còn lại trong các trình vòng lặp vào kết quả: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]

Tôi nghĩ đây là cách làm đáng sợ nhất.


3
Tại sao đây không phải là câu trả lời được chấp nhận? Đây là con trăn ngắn nhất và dài nhất và hoạt động với các độ dài danh sách khác nhau!
Jairo Vadillo

5
tên phương pháp là zip_longest không izip_longest
Jairo Vadillo

1
Vấn đề với điều này là giá trị điền vào mặc định từ zip_longest có thể ghi đè lên Nonecác * được cho là có trong danh sách. Tôi sẽ sửa trong một phiên bản tinh chỉnh để sửa lỗi này
Dubslow

Lưu ý: Điều này sẽ gây ra vấn đề nếu danh sách chứa các thành phần có giá trị False, hoặc những thứ thậm chí đó sẽ chỉ được đánh giáFalsebởi if-expression, ví dụ như một 0hoặc một danh sách trống. Điều này có thể được (một phần) tránh được bằng cách sau đây: [x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]. Tất nhiên, điều này vẫn sẽ không hoạt động nếu danh sách chứa Nonecác phần tử cần được bảo tồn. Trong trường hợp này, bạn cần thay đổi fillvalueđối số của zip_longest, như Dubslow đã đề xuất.
der_herr_g

Nonevấn đề dường như đã biến mất, ít nhất là kể từ Python 3.7.6 (tôi không biết đối với các phiên bản cũ hơn). Nếu alt_chainđược định nghĩa là def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue)), thì list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))trả về chính xác [0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99].
paime

17

Không có itertools và giả sử l1 dài hơn l2 1 mục:

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

Sử dụng itertools và giả sử rằng danh sách không chứa None:

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

Đây là câu trả lời yêu thích của tôi. Thật ngắn gọn.
mbomb007

@ anishtain4 zip lấy các cặp phần tử dưới dạng các bộ giá trị từ danh sách , [(l1[0], l2[0]), (l1[1], l2[1]), ...]. sumnối các bộ giá trị với nhau: (l1[0], l2[0]) + (l1[1], l2[1]) + ...dẫn đến danh sách xen kẽ. Phần còn lại của một dòng chỉ là phần đệm của l1 với phần tử bổ sung cho zip hoạt động và cắt cho đến -1 để loại bỏ phần đệm đó.
Zart

izip_longest (zip_longest kể từ python 3) không cần + [0] padding, nó hoàn toàn lấp đầy Không khi độ dài của danh sách không khớp, trong khi filter(None, ...(có thể sử dụng boolthay thế hoặc None.__ne__) xóa các giá trị sai, bao gồm 0, Không có và các chuỗi trống, vì vậy biểu thức thứ hai không hoàn toàn tương đương với biểu thức đầu tiên.
Zart

Câu hỏi là bạn đã làm như thế nào sumđể làm được điều đó? Vai trò của lập luận thứ hai ở đó là gì? Trong các tài liệu, đối số thứ hai là start.
anishtain 4

Giá trị mặc định của start là 0 và bạn không thể thực hiện 0+ (một số, tuple), do đó start được thay đổi thành tuple trống.
Zart

13

Tôi biết các câu hỏi hỏi về hai danh sách với một danh sách có một mục nhiều hơn danh sách kia, nhưng tôi đã nghĩ rằng tôi sẽ đặt điều này cho những người khác có thể tìm thấy câu hỏi này.

Đây là giải pháp của Duncan được điều chỉnh để hoạt động với hai danh sách có kích thước khác nhau.

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

Kết quả này cho ra:

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

6

Nếu cả hai danh sách có độ dài bằng nhau, bạn có thể thực hiện:

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

Vì danh sách đầu tiên có một phần tử nữa, bạn có thể thêm phần tử đó vào sau:

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

2
^ Đây hẳn là câu trả lời, trăn đã trở nên giống trăn hơn trong 10 năm qua
Tian

5

Đây là một lớp lót thực hiện điều đó:

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


2
Điều này hoạt động chính xác nhưng làm tôi thấy thiếu lịch sự vì nó phải làm rất nhiều để đạt được một điều gì đó quá đơn giản. Tôi không nói rằng cách tiếp cận này không hiệu quả, đơn giản là nó không đặc biệt dễ đọc.
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

Hãy rất cẩn thận khi sử dụng các đối số mặc định có thể thay đổi. Điều này sẽ chỉ trả lại câu trả lời đúng trong lần đầu tiên nó được gọi, vì lst sẽ được sử dụng lại cho mọi cuộc gọi sau đó. Điều này sẽ tốt hơn được viết là lst = None ... nếu lst là None: lst = [], mặc dù tôi không thấy lý do thuyết phục để chọn cách tiếp cận này so với những cách khác được liệt kê ở đây.
davidchambers

lst được định nghĩa bên trong hàm và một biến cục bộ cũng vậy. Vấn đề tiềm ẩn là list1 và list2 sẽ được sử dụng lại mỗi khi bạn sử dụng hàm, ngay cả khi bạn gọi hàm với các danh sách khác nhau. Xem docs.python.org/tutorial/...
blokeley

1
@blokeley: sai, sẽ được sử dụng lại nếu nó được kết hợp (list1 = [...], list2 = [...])
killown 10/09/10

Khi giải pháp này lần đầu tiên được đăng, dòng đầu tiên của nó được đọc def combine(list1, list2, lst=[]):, do đó nhận xét của tôi. Tuy nhiên, vào thời điểm tôi gửi nhận xét đó, killown đã thực hiện thay đổi cần thiết.
davidchambers

2

Điều này dựa trên đóng góp của Carlos Valiente ở trên với một tùy chọn để thay thế các nhóm gồm nhiều mục và đảm bảo rằng tất cả các mục đều có trong đầu ra:

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

Thao tác này sẽ xen kẽ danh sách A và B theo từng nhóm 3 giá trị:

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

Có thể hơi muộn để mua thêm một miếng lót trăn khác. Điều này hoạt động khi hai danh sách có kích thước bằng nhau hoặc không bằng nhau. Một điều không có giá trị gì là nó sẽ sửa đổi a và b. Nếu đó là sự cố, bạn cần sử dụng các giải pháp khác.

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

Lấy của tôi:

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

Đây là một lớp lót sử dụng tính năng hiểu danh sách, cùng với các thư viện khác:

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

Đây là một cách tiếp cận khác, nếu bạn cho phép thay đổi danh sách ban đầu của mình bằng tác dụng phụ:

[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

Dừng trên chặng ngắn nhất:

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

Chắc chắn, việc giải quyết phương thức / __ next__ tiếp theo có thể nhanh hơn.


0

Điều này thật khó chịu nhưng hoạt động bất kể kích thước của danh sách:

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

0

Nhiều lớp lót một lớp lấy cảm hứng từ các câu trả lời cho một câu hỏi khác :

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

Làm thế nào về numpy? Nó cũng hoạt động với các chuỗi:

import numpy as np

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

Kết quả:

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

0

Một giải pháp thay thế theo cách chức năng & bất biến (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

Tôi sẽ làm đơn giản:

chain.from_iterable( izip( list1, list2 ) )

Nó sẽ đi kèm với một trình lặp mà không cần tạo thêm bất kỳ nhu cầu lưu trữ nào.


1
Nó thực sự đơn giản, nhưng nó chỉ hoạt động với các danh sách có cùng độ dài!
Jochen Ritzel

Bạn có thể khắc phục sự cố với chain.from_iterable(izip(list1, list2), list1[len(list2):])sự cố cụ thể được hỏi ở đây ... list1 được cho là dài hơn.
Jochen Ritzel

Vâng, nhưng tôi muốn đưa ra một giải pháp phù hợp với các thùng chứa có chiều dài tùy ý hoặc nhường cho các giải pháp được đề xuất ở trên.
wheaties

-2

Tôi quá già để hiểu danh sách, vì vậy:

import operator
list3 = reduce(operator.add, zip(list1, list2))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.