ทำซ้ำรายการเป็นคู่ (ปัจจุบันถัดไป) ใน Python


130

บางครั้งฉันต้องวนซ้ำรายการใน Python โดยดูที่องค์ประกอบ "ปัจจุบัน" และองค์ประกอบ "ถัดไป" จนถึงตอนนี้ฉันทำด้วยรหัสเช่น:

for current, next in zip(the_list, the_list[1:]):
    # Do something

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


ตรวจสอบคำตอบของ MizardX สำหรับคำถามนี้ แต่ฉันไม่คิดว่าวิธีนี้จะเป็นสำนวนของคุณมากกว่า
Fábio Diniz


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

@senderle อาจจะเป็น Python 2 …
Quintec

2
@ thecoder16: nextยังเป็นฟังก์ชันในตัวใน Python 2
zondo

คำตอบ:


131

นี่คือตัวอย่างที่เกี่ยวข้องจากเอกสารโมดูลitertools :

import itertools
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)   

สำหรับ Python 2 คุณต้องitertools.izipใช้แทนzip:

import itertools
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)

วิธีการทำงาน:

ขั้นแรกให้ทำซ้ำสองตัวแบบขนานaและbถูกสร้างขึ้น (การtee()เรียก) ซึ่งทั้งสองชี้ไปที่องค์ประกอบแรกของการทำซ้ำดั้งเดิม ตัววนซ้ำตัวที่สองbถูกย้ายไปข้างหน้า 1 ก้าว (การnext(b, None)) เรียก) ณ จุดนี้ชี้aไปที่ s0 และbชี้ไปที่ s1 ทั้งสองaและbสามารถสำรวจตัววนซ้ำดั้งเดิมได้อย่างอิสระ - ฟังก์ชัน izip รับตัววนซ้ำสองตัวและสร้างคู่ขององค์ประกอบที่ส่งคืนโดยเลื่อนตัววนซ้ำทั้งสองในจังหวะเดียวกัน

ข้อแม้อย่างหนึ่ง: tee()ฟังก์ชันนี้สร้างตัวทำซ้ำสองตัวที่สามารถเลื่อนไปข้างหน้าอย่างอิสระจากกัน แต่มีค่าใช้จ่าย หากตัวทำซ้ำตัวใดตัวหนึ่งก้าวหน้ากว่าอีกตัวหนึ่งแสดงว่าtee() จำเป็นต้องเก็บองค์ประกอบที่ใช้ไปไว้ในหน่วยความจำจนกว่าตัววนซ้ำตัวที่สองจะเข้าใช้งานด้วย (ไม่สามารถ 'ย้อนกลับ' ตัววนซ้ำเดิมได้) ที่นี่ไม่สำคัญเพราะตัววนซ้ำหนึ่งก้าวล้ำหน้าอีก 1 ก้าว แต่โดยทั่วไปแล้วมันง่ายที่จะใช้หน่วยความจำจำนวนมากด้วยวิธีนี้

และเนื่องจากtee()สามารถใช้nพารามิเตอร์ได้จึงสามารถใช้กับตัวทำซ้ำแบบขนานได้มากกว่าสองตัว:

def threes(iterator):
    "s -> (s0,s1,s2), (s1,s2,s3), (s2, s3,4), ..."
    a, b, c = itertools.tee(iterator, 3)
    next(b, None)
    next(c, None)
    next(c, None)
    return zip(a, b, c)

4
โค้ดตัวอย่างนั้นยอดเยี่ยมมาก ... แต่คุณช่วยอธิบายหน่อยได้ไหมว่าทำไมถึงใช้งานได้? เช่นพูดว่า "tee ()" และ "next ()" กำลังทำอะไรที่นี่
John Mulder

@ จอห์นมัลเดอร์: สรุปสั้น ๆ
Rafał Dowgird

9
zip(ł, ł[1:])สั้นกว่ามากและ pythonic
noɥʇʎԀʎzɐɹƆ

2
@ noɥʇʎԀʎzɐɹƆ: ไม่มันใช้ไม่ได้กับการทำซ้ำทุกครั้งและทำสำเนาที่ไม่จำเป็นเมื่อใช้ในรายการ การใช้ฟังก์ชันคือ pythonic
Ry-

ฟังก์ชันนี้ใช้งานในfuncyโมดูลfuncy.pairwise: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR

30

ม้วนเอง!

def pairwise(iterable):
    it = iter(iterable)
    a = next(it, None)

    for b in it:
        yield (a, b)
        a = b

1
สิ่งที่ฉันต้องการ! สิ่งนี้ถูกทำให้เป็นอมตะเป็นวิธีหลามหรือไม่หรือเราต้องหมุนต่อไป?
เอ่อ

1
@uhoh: ยังไม่เท่าที่ฉันรู้!
Ry-

21

เนื่องจากthe_list[1:]สร้างสำเนาของรายการทั้งหมด (ไม่รวมองค์ประกอบแรก) และzip()สร้างรายการสิ่งที่เพิ่มขึ้นทันทีเมื่อมีการเรียกระบบจะสร้างสำเนารายการของคุณทั้งหมดสามชุด หากรายการของคุณมีขนาดใหญ่มากคุณอาจต้องการ

from itertools import izip, islice
for current_item, next_item in izip(the_list, islice(the_list, 1, None)):
    print(current_item, next_item)

ซึ่งไม่ได้คัดลอกรายการเลย


3
โปรดทราบว่าใน python 3.x izip ถูกระงับของ itertools และคุณควรใช้ builtin zip
Xavier Combelle

1
อันที่จริงแล้วไม่the_list[1:]เพียง แต่สร้างวัตถุชิ้นเล็กชิ้นน้อยแทนที่จะเป็นสำเนาของรายการเกือบทั้งหมดดังนั้นเทคนิคของ OP จึงไม่สิ้นเปลืองเท่าที่คุณทำให้มันฟัง
martineau

3
ฉันคิดว่า[1:]สร้างวัตถุชิ้น (หรืออาจเป็น " 1:") ซึ่งถูกส่งผ่านไป__slice__ยังรายการซึ่งจะส่งคืนสำเนาที่มีเฉพาะองค์ประกอบที่เลือก วิธีหนึ่งในการคัดลอกรายการคือl_copy = l[:](ซึ่งฉันคิดว่าน่าเกลียดและอ่านไม่ออก - ชอบl_copy = list(l))
dcrosta

4
@dcrosta: ไม่มี__slice__วิธีพิเศษ the_list[1:]เทียบเท่ากับการซึ่งจะเทียบเท่ากับthe_list[slice(1, None)] list.__getitem__(the_list, slice(1, None))
Sven Marnach

4
@martineau: สำเนาที่สร้างขึ้นthe_list[1:]เป็นเพียงสำเนาตื้นดังนั้นจึงประกอบด้วยตัวชี้หนึ่งตัวต่อรายการเท่านั้น ส่วนที่ต้องใช้หน่วยความจำมากขึ้นก็คือzip()ตัวมันเองเนื่องจากจะสร้างรายการของหนึ่งtupleอินสแตนซ์ต่อรายการซึ่งแต่ละรายการจะมีตัวชี้สองตัวสำหรับสองรายการและข้อมูลเพิ่มเติมบางอย่าง รายการนี้จะใช้หน่วยความจำเก้าเท่าของสำเนาที่เกิดจากการ[1:]บริโภค
Sven Marnach

19

ฉันแค่พูดออกไปฉันแปลกใจมากที่ไม่มีใครคิดจะแจกแจง ()

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something

11
จริงๆแล้วifยังสามารถลบออกได้หากคุณใช้การหั่น:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
lifebalance

2
นี่น่าจะเป็นคำตอบจริงๆไม่ต้องพึ่งพาการนำเข้าเพิ่มเติมใด ๆ และใช้งานได้ดี
jamescampbell

แม้ว่าจะใช้ไม่ได้กับการทำซ้ำที่ไม่สามารถจัดทำดัชนีได้ดังนั้นจึงไม่ใช่วิธีแก้ปัญหาทั่วไป
Wim

14

การวนซ้ำโดยดัชนีสามารถทำสิ่งเดียวกัน:

#!/usr/bin/python
the_list = [1, 2, 3, 4]
for i in xrange(len(the_list) - 1):
    current_item, next_item = the_list[i], the_list[i + 1]
    print(current_item, next_item)

เอาท์พุท:

(1, 2)
(2, 3)
(3, 4)

คำตอบของคุณเป็นแบบก่อนหน้าและเป็นปัจจุบันแทนที่จะเป็นปัจจุบันและถัดไปเหมือนในคำถาม ฉันทำการแก้ไขเพื่อปรับปรุงความหมายเพื่อให้iเป็นดัชนีขององค์ประกอบปัจจุบันเสมอ
Bengt

1

ตอนนี้เป็นการนำเข้าอย่างง่าย ณ วันที่ 16 พฤษภาคม 2020

from more_itertools import pairwise
for current, next in pairwise(your_iterable):
  print(f'Current = {current}, next = {nxt}')

เอกสารสำหรับเครื่องมือเพิ่มเติม ภายใต้ประทุนรหัสนี้เหมือนกับในคำตอบอื่น ๆ แต่ฉันชอบการนำเข้ามากเมื่อมี

หากคุณยังไม่ได้ติดตั้ง: pip install more-itertools

ตัวอย่าง

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

from more_itertools import pairwise
fib= [1,1,2,3,5,8,13]
for current, nxt in pairwise(fib):
    ratio=current/nxt
    print(f'Curent = {current}, next = {nxt}, ratio = {ratio} ')

0

จับคู่จากรายการโดยใช้ความเข้าใจในรายการ

the_list = [1, 2, 3, 4]
pairs = [[the_list[i], the_list[i + 1]] for i in range(len(the_list) - 1)]
for [current_item, next_item] in pairs:
    print(current_item, next_item)

เอาท์พุท:

(1, 2)
(2, 3)
(3, 4)

0

ฉันประหลาดใจมากที่ไม่มีใครพูดถึงวิธีแก้ปัญหาทั่วไปที่สั้นกว่าง่ายกว่าและที่สำคัญที่สุด:

Python 3:

from itertools import islice

def n_wise(iterable, n):
    return zip(*(islice(iterable, i, None) for i in range(n)))

Python 2:

from itertools import izip, islice

def n_wise(iterable, n):
    return izip(*(islice(iterable, i, None) for i in xrange(n)))

ทำงานสำหรับการวนซ้ำแบบคู่โดยการส่งผ่านn=2แต่สามารถจัดการกับจำนวนที่สูงกว่า:

>>> for a, b in n_wise('Hello!', 2):
>>>     print(a, b)
H e
e l
l l
l o
o !

>>> for a, b, c, d in n_wise('Hello World!', 4):
>>>     print(a, b, c, d)
H e l l
e l l o
l l o
l o   W
o   W o
  W o r
W o r l
o r l d
r l d !

-2

วิธีแก้ไขเบื้องต้น:

def neighbors( list ):
  i = 0
  while i + 1 < len( list ):
    yield ( list[ i ], list[ i + 1 ] )
    i += 1

for ( x, y ) in neighbors( list ):
  print( x, y )

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