Iterate daftar sebagai pasangan (saat ini, selanjutnya) dengan Python

131

Saya kadang-kadang perlu mengulang daftar dengan Python melihat elemen "saat ini" dan elemen "berikutnya". Saya, sampai sekarang, melakukannya dengan kode seperti:

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

Ini berfungsi dan melakukan apa yang saya harapkan, tetapi apakah ada cara yang lebih idiomatis atau efisien untuk melakukan hal yang sama?

dcrosta
sumber
Periksa jawaban MizardX untuk pertanyaan ini . Tetapi saya tidak berpikir solusi ini lebih idiomatis daripada milik Anda.
Fábio Diniz
2
Lihatlah Build a Basic Python Iterator .
mkluwe
39
karena tidak ada orang lain yang menyebutkannya, saya akan menjadi pria itu, dan menunjukkan bahwa menggunakan nextcara ini sebagai masker.
pengirim
@senderle Mungkin itu Python 2 ...
Quintec
2
@ thecoder16: nextjuga merupakan fungsi
bawaan

Jawaban:

131

Inilah contoh yang relevan dari dokumen modul 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)   

Untuk Python 2, Anda perlu itertools.izipbukannya 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)

Bagaimana ini bekerja:

Pertama, dua iterator paralel, adan bdibuat ( tee()panggilan), keduanya menunjuk ke elemen pertama dari iterable asli. Iterator kedua, bdigerakkan 1 langkah ke depan ( next(b, None)panggilan) panggilan. Pada titik ini menunjuk ake s0 dan bmenunjuk ke s1. Keduanya adan bdapat melintasi iterator asli secara independen - fungsi izip mengambil dua iterator dan membuat pasangan elemen yang dikembalikan, memajukan kedua iterator pada kecepatan yang sama.

Satu peringatan: tee()fungsi ini menghasilkan dua iterator yang dapat maju secara independen satu sama lain, tetapi harus dibayar. Jika salah satu iterator bergerak lebih jauh dari yang lain, maka tee() perlu menyimpan elemen yang dikonsumsi dalam memori sampai iterator kedua juga mengkonsumsinya (iterator tidak dapat 'memundurkan' iterator asli). Di sini tidak masalah karena satu iterator hanya 1 langkah di depan yang lain, tetapi secara umum mudah menggunakan banyak memori dengan cara ini.

Dan karena tee()dapat mengambil nparameter, ini juga dapat digunakan untuk lebih dari dua iterator paralel:

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)
Rafał Dowgird
sumber
4
Contoh kode sangat bagus ... tetapi, bisakah Anda memberikan sedikit penjelasan mengapa ini berhasil? Seperti mengatakan apa yang dilakukan "tee ()" dan "next ()" di sini.
John Mulder
@ John Mulder: Melakukan ringkasan singkat.
Rafał Dowgird
9
zip(ł, ł[1:])jauh lebih pendek dan pythonic
noɥʇʎԀʎzɐɹƆ
2
@ noɥʇʎԀʎzɐɹƆ: Tidak, itu tidak berfungsi di setiap iterable dan membuat salinan yang tidak perlu saat digunakan pada daftar. Menggunakan fungsi adalah pythonic.
Ry-
Fungsi ini diimplementasikan dalam funcymodul funcy.pairwise:: funcy.readthedocs.io/en/stable/seqs.html#pairwise
ADR
30

Gulung sendiri!

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

    for b in it:
        yield (a, b)
        a = b
Ry-
sumber
1
Apa yang saya butuhkan! Apakah ini telah diabadikan sebagai metode python, atau apakah kita perlu terus bergulir?
uhoh
1
@uhoh: Belum sejauh yang saya tahu!
Ry-
21

Karena the_list[1:]sebenarnya membuat salinan seluruh daftar (tidak termasuk elemen pertama), dan zip()membuat daftar tupel segera ketika dipanggil, total tiga salinan daftar Anda dibuat. Jika daftar Anda sangat besar, Anda mungkin lebih suka

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

yang tidak menyalin daftar sama sekali.

Sven Marnach
sumber
3
perhatikan bahwa dalam python 3.x izip ditekan dari itertools dan Anda harus menggunakan zip builtin
Xavier Combelle
1
Sebenarnya, tidak the_list[1:]hanya membuat objek irisan daripada salinan hampir seluruh daftar - jadi teknik OP tidak terlalu boros seperti yang Anda buat.
martineau
3
Saya pikir [1:]membuat objek slice (atau mungkin " 1:"), yang diteruskan ke __slice__dalam daftar, yang kemudian mengembalikan salinan yang hanya berisi elemen yang dipilih. Salah satu cara idiomatis untuk menyalin daftar adalah l_copy = l[:](yang saya temukan jelek dan tidak dapat dibaca - lebih disukai l_copy = list(l))
dcrosta
4
@dcrosta: Tidak ada __slice__metode khusus. the_list[1:]sama dengan the_list[slice(1, None)], yang pada gilirannya setara dengan list.__getitem__(the_list, slice(1, None)).
Sven Marnach
4
@martineau: Salinan yang dibuat oleh the_list[1:]hanya salinan dangkal, sehingga hanya terdiri dari satu pointer per item daftar. Bagian yang lebih intensif memori adalah zip()dirinya sendiri, karena ia akan membuat daftar satu tuplecontoh per item daftar, yang masing-masing akan berisi dua petunjuk untuk dua item dan beberapa informasi tambahan. Daftar ini akan mengkonsumsi sembilan kali jumlah memori yang disebabkan oleh [1:]konsumsi.
Sven Marnach
19

Saya hanya memadamkannya, saya sangat terkejut tidak ada yang berpikir untuk menghitung ().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Quintec
sumber
11
Sebenarnya, itu ifjuga dapat dihapus jika Anda menggunakan slicing:for (index, thing) in enumerate(the_list[:-1]): current, next_ = thing, the_list[index + 1]
lifebalance
2
Ini benar-benar harus menjadi jawaban, tidak bergantung pada impor tambahan dan bekerja dengan baik.
jamescampbell
Padahal, itu tidak berfungsi untuk iterables yang tidak dapat diindeks jadi itu bukan solusi umum.
wim
14

Iterasi dengan indeks dapat melakukan hal yang sama:

#!/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)

Keluaran:

(1, 2)
(2, 3)
(3, 4)
Rumple Stiltskin
sumber
Jawaban Anda lebih sebelumnya dan saat ini daripada saat ini dan berikutnya , seperti dalam pertanyaan. Saya membuat edit untuk meningkatkan semantik sehingga iselalu menjadi indeks elemen saat ini.
Bengt
1

Ini sekarang Impor sederhana pada 16 Mei 2020

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

Documents untuk lebih banyak itertools Di bawah tenda kode ini sama dengan jawaban yang lain, tapi saya lebih suka impor ketika tersedia.

Jika Anda belum menginstalnya maka: pip install more-itertools

Contoh

Misalnya jika Anda memiliki urutan fibbonnacci, Anda bisa menghitung rasio pasangan selanjutnya sebagai:

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} ')
omong kosong
sumber
0

Pasangan dari daftar menggunakan pemahaman daftar

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)

Keluaran:

(1, 2)
(2, 3)
(3, 4)
Bengt
sumber
0

Saya sangat terkejut tidak ada yang menyebutkan solusi umum yang lebih pendek, sederhana dan paling penting :

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

Ini berfungsi untuk iterasi berpasangan dengan melewati n=2, tetapi dapat menangani angka yang lebih tinggi:

>>> 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 !
Marco Bonelli
sumber
-2

Solusi dasar:

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 )
mkluwe
sumber
-2
code = '0016364ee0942aa7cc04a8189ef3'
# Getting the current and next item
print  [code[idx]+code[idx+1] for idx in range(len(code)-1)]
# Getting the pair
print  [code[idx*2]+code[idx*2+1] for idx in range(len(code)/2)]
Russell Wong
sumber