Ітерація списку як пара (поточний, наступний) в Python


131

Мені іноді потрібно повторити список на Python, дивлячись на "поточний" елемент і "наступний" елемент. Я до цього часу робив це з кодом типу:

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

Це працює і робить те, що я очікую, але чи є більш ідіоматичний чи ефективний спосіб зробити те саме?


Перевірте відповідь MizardX на це запитання . Але я не думаю, що це рішення є ідіоматичним, ніж ваше.
Фабіо Дініз


39
оскільки ніхто інший цього не згадував, я буду тим хлопцем і зазначу, що використання nextцього способу маскує вбудований.
senderle

@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:])набагато коротший і пітонічний
noɥʇʎԀʎzɐɹƆ

2
@ noɥʇʎԀʎzɐɹƆ: Ні, він не працює на кожному ітерабельному і робить непотрібну копію при використанні у списках. Використання функцій - пітонічне.
Ри-

Ця функція реалізована в 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
Тільки те, що мені було потрібно! Це було увічнено як метод пітона, чи нам потрібно продовжувати прокручуватися?
uhoh

1
@uhoh: Ще не наскільки я знаю!
Ри-

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, і вам слід використовувати вбудований zip
Xavier Combelle

1
Насправді, це не the_list[1:]просто створює об'єкт зрізу, а не копію майже всього списку - тому техніка ОП не настільки марно, наскільки ви звучите.
мартіно

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)).
Свен Марнах

4
@martineau: створена копія the_list[1:]є лише дрібною копією, тому вона складається лише з одного вказівника на елемент списку. Більш інтенсивна пам'ять - zip()сама по собі, оскільки вона створить список одного tupleекземпляра на елемент списку, кожен з яких буде містити два вказівники на два елементи та деяку додаткову інформацію. Цей список споживає дев'ять разів більше пам'яті, яку копія викликала [1:].
Свен Марнах

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]
життєвий баланс

2
Це справді має бути відповіддю, воно не покладається на будь-який додатковий імпорт і працює чудово.
jamescampbell

Хоча це не працює для неіндексуваних ітерабелів, тому це не є загальним рішенням.
Вім

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це завжди індекс поточного елемента.
Бенгт

1

Зараз це простий імпорт Станом на 16 травня 2020 року

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

Документи для більше-itertools Під кришкою цей код такий самий, як і в інших відповідях, але я більше віддаю перевагу імпорту, коли він доступний.

Якщо ви її ще не встановили: pip install more-itertools

Приклад

Наприклад, якщо у вас була послідовність фібонахін, ви можете обчислити співвідношення наступних пар як:

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

Я дуже здивований, що ніхто не згадав про коротше, простіше і найголовніше загальне рішення:

Пітон 3:

from itertools import islice

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

Пітон 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 )

-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)]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.