Попарно круговой цикл Python 'for'



есть ли хороший Питонический способ перебирать список, перенастраивая пару элементов? Последний элемент должен быть сопряжен с первым.



Так, например, если у меня есть список [1, 2, 3], я хотел бы получить следующие пары:




  • 1 - 2

  • 2 - 3

  • 3 - 1

271   17  

17 ответов:

типичный для Python способ, чтобы открыть список попарно-это: zip(L, L[1:]). Чтобы подключить последний элемент к первому:

>>> L = [1, 2, 3]
>>> zip(L, L[1:] + L[:1])
[(1, 2), (2, 3), (3, 1)]

Я бы использовал deque С zip для достижения этой цели.

>>> from collections import deque
>>>
>>> l = [1,2,3]
>>> d = deque(l)
>>> d.rotate(-1)
>>> zip(l, d)
[(1, 2), (2, 3), (3, 1)]

Я бы использовал небольшую модификацию pairwise рецепт itertools документация:

def pairwise_circle(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ... (s<last>,s0)"
    a, b = itertools.tee(iterable)
    first_value = next(b, None)
    return itertools.zip_longest(a, b,fillvalue=first_value)

это будет просто сохранить ссылку на первое значение и когда второй итератор исчерпан,zip_longest заполнить последнее место с первым значением.

(также обратите внимание, что он работает с итераторами, такими как генераторы, а также с итерациями, такими как списки/кортежи.)

отметим, что @Барри очень похоже на это, но a немного легче понять на мой взгляд и легче выйти за рамки одного элемента.

я бы пару itertools.cycle С zip:

import itertools

def circular_pairwise(l):
    second = itertools.cycle(l)
    next(second)
    return zip(l, second)

cycle возвращает итерацию, которая возвращает значения своего аргумента по порядку, циклически переходя от последнего значения к первому.

мы пропускаем первое значение, поэтому оно начинается с позиции 1 (а не 0).

далее zip С оригиналом, список многопользовательский постапокалиптический. zip хорошо, потому что он останавливается, когда любой из его аргументов iterables исчерпаны.

это позволит избежать создания промежуточных списков: cycle содержит ссылку на оригинал, но не копия его. zip работает таким же образом.

важно отметить, что это сломается, если вход iterator, например,file (или map или zip на python-3), как продвижение в одном месте (через next(second)) будет автоматически продвигать итератор во всех остальных. Это легко решается с помощью itertools.tee, который производит два независимо работающих итератора над исходным iterable:

def circular_pairwise(it):
    first, snd = itertools.tee(it)
    second = itertools.cycle(snd)
    next(second)
    return zip(first, second)

teeможете используйте большие объемы дополнительного хранилища, например, если один из возвращенных итераторов используется до того, как другой будет затронут, но поскольку у нас есть только одна разница в шагах, дополнительное хранилище минимально.

есть более эффективные способы (которые не строили временные списки), но я думаю, что это самый лаконичный:

> l = [1,2,3]
> zip(l, (l+l)[1:])
[(1, 2), (2, 3), (3, 1)]

Я бы использовал понимание списка и воспользовался тем, что l[-1] последний элемент.

>>> l = [1,2,3]
>>> [(l[i-1],l[i]) for i in range(len(l))]
[(3, 1), (1, 2), (2, 3)]

вам не нужен временный список таким образом.

попарно круговой Python' for ' loop

Если вам нравится принятый ответ,

zip(L, L[1:] + L[:1])

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

from itertools import islice, chain #, izip as zip # uncomment if Python 2

и это едва ли материализует что-либо в памяти за пределами исходного списка (предполагая, что список относительно большой):

zip(l, chain(islice(l, 1, None), islice(l, None, 1)))

чтобы использовать, просто потребляйте (например, со списком):

>>> list(zip(l, chain(islice(l, 1, None), islice(l, None, 1))))
[(1, 2), (2, 3), (3, 1)]

это может быть сделано расширяемым на любую ширину:

def cyclical_window(l, width=2):
    return zip(*[chain(islice(l, i, None), islice(l, None, i)) for i in range(width)])

и использование:

>>> l = [1, 2, 3, 4, 5]
>>> cyclical_window(l)
<itertools.izip object at 0x112E7D28>
>>> list(cyclical_window(l))
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
>>> list(cyclical_window(l, 4))
[(1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 1), (4, 5, 1, 2), (5, 1, 2, 3)]

неограниченное поколение с itertools.tee С cycle

вы также можете использовать tee чтобы избежать создания избыточного объекта цикла:

from itertools import cycle, tee
ic1, ic2 = tee(cycle(l))
next(ic2)    # must still queue up the next item

и так:

>>> [(next(ic1), next(ic2)) for _ in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]

это невероятно эффективно, ожидаемое использование iter С next, и элегантное использование cycle,tee и zip.

не проходят cycle непосредственно list если вы не сохранили свою работу и у вас есть время для вашего компьютера, чтобы ползти к остановке, как вы Макс из своей памяти - Если Вам повезет, через некоторое время ваша ОС убьет процесс, прежде чем он выйдет из строя ваш компьютер.

Pure Python Встроенные Функции

наконец, нет стандартного импорта lib, но это работает только для длины исходного списка (IndexError в противном случае.)

>>> [(l[i], l[i - len(l) + 1]) for i in range(len(l))]
[(1, 2), (2, 3), (3, 1)]

вы можете продолжить это по модулю:

>>> len_l = len(l)
>>> [(l[i % len_l], l[(i + 1) % len_l]) for i in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]

удивительно, как много разных способов решить эту проблему.

вот еще одна. Вы можете использовать pairwise рецепт, но вместо молнии с b,chain это с первым элементом,который вы уже выскочил. Не нужно cycle когда нам просто нужно одно дополнительное значение:

from itertools import chain, izip, tee

def pairwise_circle(iterable):
    a, b = tee(iterable)
    first = next(b, None)
    return izip(a, chain(b, (first,)))

мне нравится решение, которое не изменяет исходный список и не копирует список во временное хранилище:

def circular(a_list):
    for index in range(len(a_list) - 1):
        yield a_list[index], a_list[index + 1]
    yield a_list[-1], a_list[0]

for x in circular([1, 2, 3]):
    print x

выход:

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

Я могу себе представить, что это используется на некоторых очень больших данных в памяти.

это будет работать, даже если список l потребляет большую часть памяти системы. (Если что-то гарантирует, что этот случай будет невозможен, то zip, опубликованный chepner, прекрасен)

l.append( l[0] )
for i in range( len(l)-1):
   pair = l[i],l[i+1]
   # stuff involving pair
del l[-1] 

или более обобщенно (работает для любого смещения n т. е. l[ (i+n)%len(l) ])

for i in range( len(l)):
   pair = l[i], l[ (i+1)%len(l) ]
   # stuff

при условии, что вы находитесь в системе с прилично быстрым делением по модулю (т. е. не какой-то Гороховой встроенной системой).

там, кажется, часто придерживаются мнения, что индексация списка с целочисленным индексом ООН-подходящие для Python и лучше избегать. Зачем?

Это мое решение, и оно выглядит достаточно весть для меня:

l = [1,2,3]

for n,v in enumerate(l):
    try:
        print(v,l[n+1])
    except IndexError:
        print(v,l[0])

принты:

1 2
2 3
3 1

версия функции генератора:

def f(iterable):
    for n,v in enumerate(iterable):
        try:
            yield(v,iterable[n+1])
        except IndexError:
            yield(v,iterable[0])

>>> list(f([1,2,3]))
[(1, 2), (2, 3), (3, 1)]

Как насчет этого?

li = li+[li[0]]
pairwise = [(li[i],li[i+1]) for i in range(len(li)-1)]
from itertools import izip, chain, islice

itr = izip(l, chain(islice(l, 1, None), islice(l, 1)))

(Как указано выше с @J-f-ответ Себастьяна "zip", но с помощью itertools.)

NB:редактировать учитывая полезный толчок от @200_success. ранее было:

itr = izip(l, chain(l[1:], l[:1]))

просто еще одна попытка

>>> L = [1,2,3]
>>> zip(L,L[1:]) + [(L[-1],L[0])]
[(1, 2), (2, 3), (3, 1)]

Если вы не хотите, чтобы потреблять слишком много памяти, вы можете попробовать мое решение:

[(l[i], l[(i+1) % len(l)]) for i, v in enumerate(l)]

Это немного медленнее, но потребляет меньше памяти.

L = [1, 2, 3] a = zip (L, L[1:]+L[:1]) для меня в: b = Список (i) печать б

похоже, что комбинации сделают эту работу.

from itertools import combinations
x=combinations([1,2,3],2)

это даст генератор. затем это можно повторить как таковой

for i in x:
  print i

результаты будут выглядеть примерно так

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

Comments

    Ничего не найдено.