Чи є приємний пітонічний спосіб перебирати список, перенастроюючи пару елементів? Останній елемент повинен бути в парі з першим.
Так, наприклад, якщо у мене є список [1, 2, 3], я хотів би отримати наступні пари:
- 1 - 2
- 2 - 3
- 3 - 1
Чи є приємний пітонічний спосіб перебирати список, перенастроюючи пару елементів? Останній елемент повинен бути в парі з першим.
Так, наприклад, якщо у мене є список [1, 2, 3], я хотів би отримати наступні пари:
Відповіді:
Pythonic спосіб отримати доступ до списку парно є: zip(L, L[1:]). Щоб підключити останній елемент до першого:
>>> L = [1, 2, 3]
>>> zip(L, L[1:] + L[:1])
[(1, 2), (2, 3), (3, 1)]
itertoolsрішення може бути використано.
zip(крім усього іншого) досить дивно ефективні.
zip(*iterables)схожий на 3 list(zip(*iterables))- саме тому я пропоную імпортувати izip as zipдля Python 2 у своїй відповіді - stackoverflow.com/a/36918720/541136 - JF насправді передбачає Python 2 zipу цій відповіді, що поглиблює проблему непотрібного створення довгих списків в пам'яті. Відповідь JF використовує на 200% більше пам'яті, ніж потрібно для списків (8 байт на елемент), а також пам'ять для кожного кортежу (відносно величезна, 64 байти на кортеж, див. Stackoverflow.com/a/30316760/541136 ) на 800% більше, тож на 1000% більше загальної кількості (припускаючи ледачу оцінку.)
deque.rotateщо може бути надзвичайно корисним.
Я б використав невелику модифікацію 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 дуже схоже на це, але дещо легше зрозуміти, на мій погляд, і легше вийти за рамки одного елемента.
cycle.
itertools.zip_longest(a, b, fillvalue=first_value) це очевидніше izip(a, chain(b, (first,))) - не потрібно робити зайвий кортеж(first, )
Я б поєднався itertools.cycleз zip:
import itertools
def circular_pairwise(l):
second = itertools.cycle(l)
next(second)
return zip(l, second)
cycle повертає ітерабель, який видає значення аргументу в порядку, переходячи від останнього значення до першого.
Ми пропускаємо перше значення, тому воно починається з позиції 1(а не0 ).
Далі ми zipвикористовуємо оригінальний, немутований список. zipце добре, оскільки він зупиняється, коли будь-який його ітерабель аргументів вичерпується.
Це робить таким чином, щоб уникнути створення проміжних списків: cycleзберігає посилання на оригінал, але не копіює його. zipдіє так само.
Важливо зауважити, що це призведе до поломки, якщо вхідне значення є iteratorтаким, як a file, (або amap або zipinpython-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 може використовувати великий обсяг додаткового сховища, наприклад, якщо один із повернутих ітераторів використовується до того, як торкнуться іншого, але, оскільки у нас коли-небудь є лише один крок, додатковий обсяг є мінімальним.
itertools, а функціональне програмування в цілому дозволяє надзвичайно чистий, стислий код, і при цьому його легко читати.
cycleдокументах конкретно заявляють , що вони зберегти копію кожного елемента на вході Iterable - так що ви не економити будь-який простір над зробити копію списку. Деякі інші відповіді дозволяють уникнути цієї проблеми, використовуючи chainабо fillvalue.
Є більш ефективні способи (які не створюють тимчасові списки), але я думаю, що це найбільш стисло:
> l = [1,2,3]
> zip(l, (l+l)[1:])
[(1, 2), (2, 3), (3, 1)]
(l+l)удвічі більший.
Я б використав розуміння списку та скористався тим, що l[-1]є останнім елементом.
>>> l = [1,2,3]
>>> [(l[i-1],l[i]) for i in range(len(l))]
[(3, 1), (1, 2), (2, 3)]
Вам не потрібен тимчасовий список таким чином.
xrangePython 2, щоб уникнути тимчасового списку. Створення списку з числа від 1 до n так само дорого, як створення дрібної копії списку з nелементами.
[(l[i-1], it) for i, it in enumerate(l)]. Напевно, слід зауважити, що обидва ці методи ставлять останній запитуваний елемент першим.
Паровий круговий цикл Python 'for'
Якщо вам подобається прийнята відповідь,
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)]
Ви можете продовжити це за модулем:
>>> 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)]
range(10)), цикл ніколи не закінчиться.
Вражаюче, скільки різних способів вирішити цю проблему.
Ось ще один. Ви можете використовувати 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
за умови, що ви працюєте в системі з пристойно швидким модульним розподілом (тобто не в якійсь вбудованій системі з мозком).
Здається, існує поширена думка, що індексація списку цілочисельним індексом є непітонічною і найкраще уникати. Чому?
for i in range(len(a_list)): print a_list[i]замість for x in a_list: print x.
for el in iterable:це шлях. Якщо вам також потрібен індекс, enumerate()це шлях. Тому слід уникати ручного індексування.
Це моє рішення, і воно мені здається досить пітонічним:
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)))
(Як і вище у відповіді @ zf-sebastian "zip" , але за допомогою itertools.)
Примітка: ВИДАЛЕНО, надано корисний поштовх від @ 200_success . раніше було:
itr = izip(l, chain(l[1:], l[:1]))
itertools, тоді рішення @ RoadieRich краще, оскільки воно дозволяє уникнути копіювання та нарізки.
L = [1, 2, 3] a = zip (L, L [1:] + L [: 1]) для i в a: b = list (i) print b
У майбутньому Python 3.10 release scheduleнова pairwiseфункція забезпечує спосіб створення розсувних пар послідовних елементів:
from itertools import pairwise
# l = [1, 2, 3]
list(pairwise(l + l[:1]))
# [(1, 2), (2, 3), (3, 1)]
або просто pairwise(l + l[:1])якщо вам не потрібен результат як list.
Зверніть увагу, що ми pairwiseв списку додані з його head ( l + l[:1]) так, щоб прокатні пари були круговими (тобто, щоб ми також включили (3, 1)пару):
list(pairwise(l)) # [(1, 2), (2, 3)]
l + l[:1] # [1, 2, 3, 1]
здається, комбінації зробили б цю роботу.
from itertools import combinations
x=combinations([1,2,3],2)
це дало б генератор. це потім можна повторити як таке
for i in x:
print i
результати виглядали б приблизно так
(1, 2)
(1, 3)
(2, 3)
[1,2,3,4,5]результат, [(1,2), (2,3), (3,4), (4,5), (5,1)]який буде не комбінаціями, а лише парами у списку.