Итерировать список как пару (текущий, следующий) в Python

131

Иногда мне нужно перебирать список в Python, глядя на «текущий» элемент и «следующий» элемент. До сих пор я делал это с помощью кода вроде:

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

Это работает и делает то, что я ожидаю, но есть ли более идиоматичный или эффективный способ сделать то же самое?

dcrosta
источник
Проверьте ответ MizardX на этот вопрос . Но я не думаю, что это решение более идиоматично, чем ваше.
Fábio Diniz,
2
Взгляните на сборку базового итератора Python .
mkluwe
39
так как никто больше не упомянул об этом, я буду тем парнем и укажу, что использование nextэтого способа маскирует встроенный.
senderle
@senderle Может быть, это Python 2…
Quintec 02
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:])намного короче и
питонический
2
@ noɥʇʎԀʎzɐɹƆ: Нет, он не работает для каждой итерации и создает ненужную копию при использовании в списках. Использование функций питонично.
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
Ry-
источник
1
Как раз то, что мне было нужно! Это было увековечено как метод Python, или нам нужно продолжать работу?
uhoh
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, и вы должны использовать встроенный zip
Xavier Combelle
1
На самом деле, это не the_list[1:]просто создание объекта-среза, а не копия почти всего списка - так что техника OP не так расточительна, как вы говорите.
Мартино
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

Я просто выкладываю это и очень удивлен, что никто не подумал о enumerate ().

for (index, thing) in enumerate(the_list):
    if index < len(the_list):
        current, next_ = thing, the_list[index + 1]
        #do something
Quintec
источник
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}')

Документы для more-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

Я действительно удивлен, что никто не упомянул более короткое, простое и, самое главное, общее решение:

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 )
mkluwe
источник
-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)]
Рассел Вонг
источник