คู่จากรายการเดียว


101

บ่อยครั้งที่ฉันพบว่าจำเป็นต้องประมวลผลรายการทีละคู่ ฉันสงสัยว่าวิธีใดจะเป็นวิธีที่มีประสิทธิภาพและมีประสิทธิภาพและพบสิ่งนี้ใน Google

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

ฉันคิดว่านั่นเพียงพอแล้ว แต่หลังจากการสนทนาล่าสุดเกี่ยวกับสำนวนเทียบกับประสิทธิภาพฉันตัดสินใจทำการทดสอบบางอย่าง:

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

นี่คือผลลัพธ์บนคอมพิวเตอร์ของฉัน:

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

ถ้าฉันตีความอย่างถูกต้องนั่นควรหมายความว่าการใช้รายการการจัดทำดัชนีรายการและการแบ่งส่วนรายการใน Python นั้นมีประสิทธิภาพมาก เป็นผลลัพธ์ที่ทั้งสบายใจและไม่คาดคิด

มีวิธีอื่นที่ "ดีกว่า" ในการสำรวจรายการเป็นคู่หรือไม่?

โปรดทราบว่าหากรายการมีองค์ประกอบจำนวนคี่รายการสุดท้ายจะไม่อยู่ในคู่ใด ๆ

วิธีใดเป็นวิธีที่ถูกต้องเพื่อให้แน่ใจว่าองค์ประกอบทั้งหมดรวมอยู่ด้วย

ฉันได้เพิ่มคำแนะนำสองข้อนี้จากคำตอบของการทดสอบ:

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

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

นี่คือผลลัพธ์:

0.00159502029419 1.25745987892 1.25586485863
0.00222492218018 1.23795199394 1.23572707176

ผลลัพธ์จนถึงตอนนี้

pythonic ส่วนใหญ่และมีประสิทธิภาพมาก:

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

มีประสิทธิภาพมากที่สุดและ pythonic มาก:

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

ฉันใช้เวลาสักครู่ในการคร่ำครวญว่าคำตอบแรกใช้ตัวทำซ้ำสองตัวในขณะที่คำตอบที่สองใช้ตัวเดียว

เพื่อจัดการกับลำดับด้วยเลขคี่ขององค์ประกอบข้อเสนอแนะที่ได้รับเพื่อเพิ่มลำดับเดิมเพิ่มองค์ประกอบหนึ่ง ( None) itertools.izip_longest()ที่ได้รับการจับคู่กับองค์ประกอบสุดท้ายก่อนหน้านี้สิ่งที่สามารถทำได้ด้วย

สุดท้าย

โปรดสังเกตว่าใน Python 3.x zip()ทำงานเป็นitertools.izip()และitertools.izip() หายไป


RE: "ทางที่ถูกต้อง" - ไม่มีทาง "ถูก"! ขึ้นอยู่กับกรณีการใช้งาน
Andrew Jaffe

@Andrew Jaffe ฉันให้เกณฑ์สำหรับ "ดีที่สุด" ในกรณีนี้: มีประสิทธิภาพและไพโธนิก
Apalala

@Apalala: ฉันหมายความว่าผลลัพธ์ของการมีเลขคี่ขึ้นอยู่กับการใช้งาน ตัวอย่างเช่นคุณสามารถเว้นองค์ประกอบสุดท้ายไว้หรือเพิ่มองค์ประกอบจำลองที่รู้จักเฉพาะหรือทำซ้ำองค์ประกอบสุดท้าย
Andrew Jaffe

2
@Apalala: เพราะคุณใช้ mumbo-jumbo แทนtimeitโมดูล
SilentGhost

1
n-duplicated: ในการค้นหาอย่างรวดเร็ว: stackoverflow.com/questions/4501636 , stackoverflow.com/questions/4170295 , stackoverflow.com/questions/434287
tokland

คำตอบ:


53

วิธีที่ฉันชอบทำ:

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)

เมื่อคุณต้องการจับคู่องค์ประกอบทั้งหมดคุณอาจต้องมีค่าเติม:

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

ฟังก์ชั่นแรก (pairwise) ดูเหมือนจะไม่มีการโคลนนิ่งและความก้าวหน้าของตัววนซ้ำที่สอง ดูitertoolsส่วนสูตรอาหาร
Apalala

@Apalala: zip เลื่อนตัวทำซ้ำเดียวกันสองครั้ง
Jochen Ritzel

แน่นอนคุณถูกต้องและการจับคู่มีประสิทธิภาพมากที่สุดจนถึงตอนนี้ฉันไม่รู้ว่าทำไม
Apalala

1
ฉันชอบวิธีแก้ปัญหานี้มันขี้เกียจและใช้ประโยชน์จากสถานะของตัวทำซ้ำเพื่อให้ได้ผลอย่างมาก คุณสามารถทำให้มันเป็นซับเดียวได้แม้ว่าอาจจะต้องเสียค่าใช้จ่ายในการอ่าน:izip(*[iter(t)]*size)
Channing Moore

สำหรับวิธีที่สองของคุณคุณไม่ต้องการหลีกเลี่ยงการสร้างรายการหากดำเนินการต่อหรือไม่?
สูงสุด

42

ฉันจะบอกว่าโซลูชันเริ่มต้นของคุณpairs = zip(t[::2], t[1::2])เป็นวิธีที่ดีที่สุดเพราะอ่านง่ายที่สุด (และใน Python 3 zipจะส่งคืนตัววนซ้ำโดยอัตโนมัติแทนที่จะเป็นรายการ)

Noneเพื่อให้แน่ใจว่าทุกองค์ประกอบที่จะถูกรวมคุณก็สามารถขยายรายการโดย

(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

ฉันเริ่มต้นด้วยข้อจำกัดความรับผิดชอบเล็กน้อย - อย่าใช้รหัสด้านล่าง ไม่ใช่ Pythonic เลยฉันเขียนเพื่อความสนุก คล้ายกับpairwiseฟังก์ชัน@ THC4k แต่ใช้iterและlambdaปิด มันไม่ได้ใช้โมดูลและไม่สนับสนุนการitertools fillvalueฉันวางไว้ที่นี่เพราะอาจมีคนคิดว่ามันน่าสนใจ:

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

4

เท่าที่ pythonic ส่วนใหญ่ไปฉันจะบอกว่าสูตรอาหารที่ให้มาในเอกสารแหล่งที่มาของ python (บางส่วนดูเหมือนคำตอบที่ @JochenRitzel ให้ไว้) อาจเป็นทางออกที่ดีที่สุด

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)

ใน python สมัยใหม่คุณต้องใช้zip_longest(*args, fillvalue=fillvalue) ตามหน้าเอกสารที่เกี่ยวข้อง


2

มีวิธีอื่นที่ "ดีกว่า" ในการสำรวจรายการเป็นคู่หรือไม่?

ฉันไม่สามารถพูดได้อย่างแน่นอน แต่ฉันสงสัย: การข้ามผ่านอื่น ๆ จะรวมโค้ด Python เพิ่มเติมซึ่งต้องตีความ ฟังก์ชันในตัวเช่น zip () เขียนด้วยภาษา C ซึ่งเร็วกว่ามาก

วิธีใดเป็นวิธีที่ถูกต้องเพื่อให้แน่ใจว่าองค์ประกอบทั้งหมดรวมอยู่ด้วย

ตรวจสอบความยาวของรายการและหากเป็นเลขคี่ ( len(list) & 1 == 1) ให้คัดลอกรายการและต่อท้ายรายการ


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: pop from empty list
HQuser

@HQuser แน่นอนว่าคุณจะได้รับข้อผิดพลาดนั้นหากคุณมีรายการจำนวนคี่ในรายการ คุณต้องรู้แน่ว่าคุณมีคู่หรือตรวจสอบเงื่อนไขข้อผิดพลาดนี้
WaterMolecule

1

ทำเท่านั้น:

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

รหัสของคุณเทียบเท่ากับรหัสที่ง่ายกว่าlist(zip(l, l[1:]))และจะไม่แยกรายการออกเป็นคู่
Apalala

0

นี่คือตัวอย่างของการสร้างคู่ / ขาโดยใช้เครื่องกำเนิดไฟฟ้า เครื่องกำเนิดไฟฟ้าไม่มีขีด จำกัด สแต็ก

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

ตัวอย่าง:

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

เอาท์พุต:

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

เปรียบเทียบเวลาดำเนินการ?
อลัน

รายการจะไม่แยกเป็นคู่เนื่องจากตัวเลขส่วนใหญ่ในรายการเดิมจะปรากฏเป็นสองตัว ผลผลิตที่คาดหวังคือ[(0, 1), (2, 3), (4, 5)....
Apalala

@Apalala ขอบคุณสำหรับการชี้ให้เห็น ฉันแก้ไขรหัสเพื่อให้ได้ผลลัพธ์ที่ถูกต้อง
Vlad Bezden

zip()ส่งคืนเครื่องกำเนิดไฟฟ้าแล้วใน Python 3.x, @VladBezden
Apalala

หากความยาวของรายการไม่เท่ากันองค์ประกอบล่าสุดจะถูกทิ้ง
Daniil Okhlopkov

-1

ในกรณีที่มีคนต้องการอัลกอริทึมคำตอบที่ชาญฉลาดนี่คือ:

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

แต่โปรดทราบว่ารายการเดิมของคุณจะลดลงเป็นองค์ประกอบสุดท้ายด้วยเนื่องจากคุณใช้popมัน

>>> k
[4]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.