Есть ли хороший метод Pythonic для перебора списка, перенастройка пары элементов? Последний элемент должен быть сопряжен с первым.
Так, например, если у меня есть список [1, 2, 3], я бы хотел получить следующие пары:
- 1 - 2
- 2 - 3
- 3 - 1
Есть ли хороший метод Pythonic для перебора списка, перенастройка пары элементов? Последний элемент должен быть сопряжен с первым.
Так, например, если у меня есть список [1, 2, 3], я бы хотел получить следующие пары:
Pythonic способ доступа к списку попарно: 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
заполнит последнее место первым значением.
(Также обратите внимание, что он работает с итераторами, такими как генераторы, а также итерами, такими как списки/кортежи.)
Обратите внимание, что @Barry solution очень похоже на это, но немного легче понять, на мой взгляд, и проще выходить за рамки одного элемента.
Я бы пару 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
, который создает два независимо работающих итератора по исходному итерабельному:
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)]
Вам не нужен временный список.
Pairwise круговой 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
, если вы не сохранили свою работу и не успели зайти в компьютер, когда вы исчерпали свою память - если вам повезет, через некоторое время ваша ОС будет убивать процесс, прежде чем он выйдет из строя на вашем компьютере.
Наконец, никаких стандартных импортов lib, но это работает только до длины исходного списка (IndexError в противном случае.)
>>> [(l[i], l[i - len(l) + 1]) for i in range(len(l))]
[(1, 2), (2, 3), (3, 1)]
Вы можете продолжить это с помощью modulo:
>>> 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
, но вместо zipping с помощью 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
i.e. l[ (i+n)%len(l) ]
)
for i in range( len(l)):
pair = l[i], l[ (i+1)%len(l) ]
# stuff
при условии, что вы находитесь в системе с прилично быстрым модульным разделением (т.е. не с одной голосовой вставкой системы).
Похоже, что часто считается, что индексирование списка с целым индексом является непитоновым и лучше всего избегать. Зачем?
Это мое решение, и для меня это выглядит достаточно Pythonic:
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-sebastian "zip" answer, но используя itertools.)
NB: EDITED, учитывая полезный подталкивание от @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) print b
похоже, что комбинации выполняли бы эту работу.
from itertools import combinations
x=combinations([1,2,3],2)
это даст генератор. это можно затем повторить как таковое
for i in x:
print i
результаты выглядят примерно так:
(1, 2)
(1, 3)
(2, 3)