Các cặp từ một danh sách


98

Thông thường, tôi thấy cần phải xử lý danh sách theo từng cặp. Tôi đang băn khoăn không biết đâu sẽ là cách hiệu quả và tốt để làm điều đó, và tìm thấy điều này trên Google:

pairs = zip(t[::2], t[1::2])

Tôi nghĩ rằng điều đó đã đủ khó hiểu, nhưng sau một cuộc thảo luận gần đây liên quan đến thành ngữ và hiệu quả , tôi quyết định thực hiện một số thử nghiệm:

import time
from itertools import islice, izip

def pairs_1(t):
    return zip(t[::2], t[1::2]) 

def pairs_2(t):
    return izip(t[::2], t[1::2]) 

def pairs_3(t):
    return izip(islice(t,None,None,2), islice(t,1,None,2))

A = range(10000)
B = xrange(len(A))

def pairs_4(t):
    # ignore value of t!
    t = B
    return izip(islice(t,None,None,2), islice(t,1,None,2))

for f in pairs_1, pairs_2, pairs_3, pairs_4:
    # time the pairing
    s = time.time()
    for i in range(1000):
        p = f(A)
    t1 = time.time() - s

    # time using the pairs
    s = time.time()
    for i in range(1000):
        p = f(A)
        for a, b in p:
            pass
    t2 = time.time() - s
    print t1, t2, t2-t1

Đây là kết quả trên máy tính của tôi:

1.48668909073 2.63187503815 1.14518594742
0.105381965637 1.35109519958 1.24571323395
0.00257992744446 1.46182489395 1.45924496651
0.00251388549805 1.70076990128 1.69825601578

Nếu tôi diễn giải chúng một cách chính xác, điều đó có nghĩa là việc triển khai danh sách, lập chỉ mục danh sách và phân loại danh sách trong Python rất hiệu quả. Đó là một kết quả vừa an ủi vừa bất ngờ.

Có cách nào khác "tốt hơn" để duyệt danh sách theo cặp không?

Lưu ý rằng nếu danh sách có một số phần tử lẻ thì phần tử cuối cùng sẽ không nằm trong bất kỳ cặp nào.

Cách nào sẽ là cách phù hợp để đảm bảo rằng tất cả các yếu tố đều được bao gồm?

Tôi đã thêm hai gợi ý này từ câu trả lời cho các bài kiểm tra:

def pairwise(t):
    it = iter(t)
    return izip(it, it)

def chunkwise(t, size=2):
    it = iter(t)
    return izip(*[it]*size)

Đây là những kết quả:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

Kết quả cho đến nay

Hầu hết pythonic và rất hiệu quả:

pairs = izip(t[::2], t[1::2])

Hiệu quả nhất và rất hiệu quả:

pairs = izip(*[iter(t)]*2)

Tôi mất một lúc để tìm hiểu rằng câu trả lời đầu tiên sử dụng hai trình vòng lặp trong khi câu trả lời thứ hai sử dụng một trình vòng lặp duy nhất.

Để đối phó với các chuỗi có số phần tử lẻ, đề xuất là tăng chuỗi ban đầu bằng cách thêm một phần tử ( None) được ghép nối với phần tử cuối cùng trước đó, một cái gì đó có thể đạt được itertools.izip_longest().

Cuối cùng

Lưu ý rằng, trong Python 3.x, zip()hoạt động như itertools.izip()itertools.izip() không còn nữa.


RE: "đúng cách" - không có một cách "đúng"! Nó phụ thuộc vào trường hợp sử dụng.
Andrew Jaffe

@Andrew Jaffe Tôi đã đưa ra tiêu chí cho "tốt nhất" trong trường hợp này: hiệu quả và tốt.
Apalala

@Apalala: Ý tôi là kết quả của việc có một số lẻ tùy thuộc vào việc sử dụng. Ví dụ: bạn chỉ có thể rời khỏi yếu tố cuối cùng, hoặc thêm một yếu tố giả cụ thể được biết đến, hoặc lặp lại người cuối cùng
Andrew Jaffe

2
@Apalala: bởi vì bạn đang sử dụng một số mumbo-jumbo thay vì timeitmô-đun.
SilentGhost

Câu trả lời:


52

Cách yêu thích của tôi để làm điều đó:

from itertools import izip

def pairwise(t):
    it = iter(t)
    return izip(it,it)

# for "pairs" of any length
def chunkwise(t, size=2):
    it = iter(t)
    return izip(*[it]*size)

Khi bạn muốn ghép nối tất cả các phần tử, rõ ràng bạn có thể cần một giá trị lấp đầy:

from itertools import izip_longest
def blockwise(t, size=2, fillvalue=None):
    it = iter(t)
    return izip_longest(*[it]*size, fillvalue=fillvalue)

Hàm đầu tiên (theo cặp) dường như thiếu tính năng sao chép và tăng tiến của trình lặp thứ hai. Xem phần itertoolscông thức nấu ăn.
Apalala

@Apalala: zip tiến tới cùng một trình lặp hai lần.
Jochen Ritzel

Tất nhiên, bạn nói đúng, và theo cặp là hiệu quả nhất cho đến nay, tôi không biết tại sao.
Apalala

1
Tôi thích giải pháp này: nó lười biếng và nó khai thác trạng thái của các trình vòng lặp để có hiệu quả tuyệt vời. Bạn thậm chí có thể làm cho nó một lớp lót, mặc dù có lẽ phải trả giá bằng khả năng đọc:izip(*[iter(t)]*size)
Channing Moore

cho giải pháp thứ hai của bạn, bạn có muốn tránh tạo danh sách nếu chạy theo hiệu suất không?
tối đa

40

Tôi muốn nói rằng giải pháp ban đầu của bạn pairs = zip(t[::2], t[1::2])là giải pháp tốt nhất vì nó dễ đọc nhất (và trong Python 3, ziptự động trả về một trình lặp thay vì danh sách).

Để đảm bảo rằng tất cả các yếu tố được bao gồm, bạn chỉ cần mở rộng danh sách bằng cách None.

Sau đó, nếu danh sách có một số phần tử lẻ, cặp cuối cùng sẽ là (item, None).

>>> t = [1,2,3,4,5]
>>> t.append(None)
>>> zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, None)]
>>> t = [1,2,3,4,5,6]
>>> t.append(None)
>>> zip(t[::2], t[1::2])
[(1, 2), (3, 4), (5, 6)]

6

Tôi bắt đầu với tuyên bố từ chối trách nhiệm nhỏ - không sử dụng mã bên dưới. Nó hoàn toàn không phải là Pythonic, tôi viết chỉ cho vui thôi. Nó tương tự như hàm @ THC4k pairwisenhưng nó sử dụng iterlambdađóng. Nó không sử dụng itertoolsmô-đun và không hỗ trợ fillvalue. Tôi đặt nó ở đây vì ai đó có thể thấy nó thú vị:

pairwise = lambda t: iter((lambda f: lambda: (f(), f()))(iter(t).next), None)

3

Theo như hầu hết các loài trăn , tôi muốn nói rằng các công thức nấu ăn được cung cấp trong các tài liệu về nguồn trăn (một số trong số đó trông rất giống câu trả lời mà @JochenRitzel đã cung cấp) có lẽ là lựa chọn tốt nhất của bạn;)

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

2

Có cách nào khác "tốt hơn" để duyệt danh sách theo cặp không?

Tôi không thể nói chắc chắn nhưng tôi nghi ngờ điều đó: Bất kỳ trình duyệt nào khác sẽ bao gồm nhiều mã Python hơn phải được thông dịch. Các hàm tích hợp như zip () được viết bằng C, nhanh hơn nhiều.

Cách nào sẽ là cách phù hợp để đảm bảo rằng tất cả các yếu tố đều được bao gồm?

Kiểm tra độ dài của danh sách và nếu nó là số lẻ ( len(list) & 1 == 1), hãy sao chép danh sách và thêm một mục.


2
>>> my_list = [1,2,3,4,5,6,7,8,9,10]
>>> my_pairs = list()
>>> while(my_list):
...     a = my_list.pop(0); b = my_list.pop(0)
...     my_pairs.append((a,b))
... 
>>> print(my_pairs)
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]

IndexError: bật ra từ danh sách trống
HQuser

@HQuser Chắc chắn, bạn sẽ gặp lỗi đó nếu bạn có một số mục lẻ trong danh sách. Bạn phải biết chắc chắn rằng bạn có các cặp hoặc kiểm tra tình trạng lỗi này.
WaterMolecule

0

Chỉ làm điều đó:

>>> l = [1, 2, 3, 4, 5, 6]
>>> [(x,y) for x,y in zip(l[:-1], l[1:])]
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]

Mã của bạn tương đương với mã đơn giản hơn list(zip(l, l[1:]))và nó không chia danh sách thành từng cặp.
Apalala

0

Đây là một ví dụ về việc tạo cặp / chân bằng cách sử dụng trình tạo. Máy phát điện không có giới hạn ngăn xếp

def pairwise(data):
    zip(data[::2], data[1::2])

Thí dụ:

print(list(pairwise(range(10))))

Đầu ra:

[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

So sánh thời gian thực hiện?
Alan

Danh sách không được chia thành từng cặp, vì hầu hết các số trong danh sách ban đầu xuất hiện trong hai bộ giá trị. Sản lượng dự kiến ​​là[(0, 1), (2, 3), (4, 5)....
Apalala

@Apalala cảm ơn bạn đã chỉ ra. Tôi đã sửa mã để cung cấp đầu ra phù hợp
Vlad Bezden

zip()đã trả về một trình tạo bằng Python 3.x, @VladBezden
Apalala

-1

Chỉ trong trường hợp ai đó cần thuật toán trả lời, đây là:

>>> def getPairs(list):
...     out = []
...     for i in range(len(list)-1):
...         a = list.pop(0)
...         for j in a:
...             out.append([a, j])
...     return b
>>> 
>>> k = [1, 2, 3, 4]
>>> l = getPairs(k)
>>> l
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

Nhưng hãy lưu ý rằng danh sách ban đầu của bạn cũng sẽ bị giảm xuống thành phần cuối cùng của nó, vì bạn đã sử dụng popnó.

>>> k
[4]
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.