Itérer sur les paires dans une liste (circulaires) en Python
le problème est facile, je veux itérer sur chaque élément de la liste et le suivant en paires (enveloppant le dernier avec le premier).
j'ai réfléchi à deux façons impythoniques de le faire:
def pairs(lst):
n = len(lst)
for i in range(n):
yield lst[i],lst[(i+1)%n]
et:
def pairs(lst):
return zip(lst,lst[1:]+[lst[:1]])
résultats escomptés:
>>> for i in pairs(range(10)):
print i
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
(9, 0)
>>>
des suggestions sur une façon plus pythonique de faire cela? peut-être qu'il y a une fonction prédéfinie là-bas je n'ai pas entendu ?
aussi un n-fold plus général (avec triplets, quatuors, etc. au lieu de paires) la version pourrait être intéressante.
13 réponses
def pairs(lst):
i = iter(lst)
first = prev = item = i.next()
for item in i:
yield prev, item
prev = item
yield item, first
fonctionne sur toute séquence non vide, aucune indexation requise.
j'ai codé moi-même les versions générales de tuple, j'aime la première pour sa simplicité élégante, plus je la regarde, plus Pythonic il se sent pour moi... après tout, qu'est-ce qui est plus Pythonic qu'un liner unique avec zip, l'extension d'argument asterisk, les compréhensions de liste, le découpage de liste, la concaténation de liste et la "portée"?
def ntuples(lst, n):
return zip(*[lst[i:]+lst[:i] for i in range(n)])
la version itertools devrait être assez efficace même pour les grandes listes...
from itertools import *
def ntuples(lst, n):
return izip(*[chain(islice(lst,i,None), islice(lst,None,i)) for i in range(n)])
et une version pour les séquences non indexables:
from itertools import *
def ntuples(seq, n):
iseq = iter(seq)
curr = head = tuple(islice(iseq, n))
for x in chain(iseq, head):
yield curr
curr = curr[1:] + (x,)
en tout cas, merci à tous pour vos suggestions! :- )
j'ai, comme toujours, comme en té:
from itertools import tee, izip, chain
def pairs(iterable):
a, b = tee(iterable)
return izip(a, chain(b, [next(b)]))
cela pourrait être satisfaisant:
def pairs(lst):
for i in range(1, len(lst)):
yield lst[i-1], lst[i]
yield lst[-1], lst[0]
>>> a = list(range(5))
>>> for a1, a2 in pairs(a):
... print a1, a2
...
0 1
1 2
2 3
3 4
4 0
si vous aimez ce genre de choses, regardez les articles de python sur wordaligned.org . L'auteur a un amour particulier pour les générateurs en python.
je le ferais comme ceci (surtout parce que je peux lire ceci):
class Pairs(object):
def __init__(self, start):
self.i = start
def next(self):
p, p1 = self.i, self.i + 1
self.i = p1
return p, p1
def __iter__(self):
return self
if __name__ == "__main__":
x = Pairs(0)
y = 1
while y < 20:
print x.next()
y += 1
donne:
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
[(i,(i+1)%len(range(10))) for i in range(10)]
remplacer la portée (10) par la liste que vous voulez.
en général "indexation circulaire" est assez facile en python; il suffit d'utiliser:
a[i%len(a)]
pour répondre à votre question sur la résolution pour le cas général:
import itertools
def pair(series, n):
s = list(itertools.tee(series, n))
try:
[ s[i].next() for i in range(1, n) for j in range(i)]
except StopIteration:
pass
while True:
result = []
try:
for j, ss in enumerate(s):
result.append(ss.next())
except StopIteration:
if j == 0:
break
else:
s[j] = iter(series)
for ss in s[j:]:
result.append(ss.next())
yield result
la sortie est comme ceci:
>>> for a in pair(range(10), 2):
... print a
...
[0, 1]
[1, 2]
[2, 3]
[3, 4]
[4, 5]
[5, 6]
[6, 7]
[7, 8]
[8, 9]
[9, 0]
>>> for a in pair(range(10), 3):
... print a
...
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 0]
[9, 0, 1]
cela tourne à l'infini, pour le bien ou le mal, mais est algorithmiquement très clair.
from itertools import tee, cycle
def nextn(iterable,n=2):
''' generator that yields a tuple of the next n items in iterable.
This generator cycles infinitely '''
cycled = cycle(iterable)
gens = tee(cycled,n)
# advance the iterators, this is O(n^2)
for (ii,g) in zip(xrange(n),gens):
for jj in xrange(ii):
gens[ii].next()
while True:
yield tuple([x.next() for x in gens])
def test():
data = ((range(10),2),
(range(5),3),
(list("abcdef"),4),)
for (iterable, n) in data:
gen = nextn(iterable,n)
for j in range(len(iterable)+n):
print gen.next()
test()
donne:
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
(5, 6)
(6, 7)
(7, 8)
(8, 9)
(9, 0)
(0, 1)
(1, 2)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, 0)
(4, 0, 1)
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')
('e', 'f', 'a', 'b')
('f', 'a', 'b', 'c')
('a', 'b', 'c', 'd')
('b', 'c', 'd', 'e')
('c', 'd', 'e', 'f')
('d', 'e', 'f', 'a')
version encore plus courte de la solution zip * range de Fortran (avec lambda cette fois;):
group = lambda t, n: zip(*[t[i::n] for i in range(n)])
group([1, 2, 3, 3], 2)
donne:
[(1, 2), (3, 4)]
Voici une version qui supporte un index de démarrage optionnel (par exemple pour retourner (4, 0) comme première paire, utilisez start = -1:
import itertools
def iterrot(lst, start = 0):
if start == 0:
i = iter(lst)
elif start > 0:
i1 = itertools.islice(lst, start, None)
i2 = itertools.islice(lst, None, start)
i = itertools.chain(i1, i2)
else:
# islice doesn't support negative slice indices so...
lenl = len(lst)
i1 = itertools.islice(lst, lenl + start, None)
i2 = itertools.islice(lst, None, lenl + start)
i = itertools.chain(i1, i2)
return i
def iterpairs(lst, start = 0):
i = iterrot(lst, start)
first = prev = i.next()
for item in i:
yield prev, item
prev = item
yield prev, first
def itertrios(lst, start = 0):
i = iterrot(lst, start)
first = prevprev = i.next()
second = prev = i.next()
for item in i:
yield prevprev, prev, item
prevprev, prev = prev, item
yield prevprev, prev, first
yield prev, first, second
def pairs(ex_list):
for i, v in enumerate(ex_list):
if i < len(list) - 1:
print v, ex_list[i+1]
else:
print v, ex_list[0]
Enumerate retourne un tuple avec l'indice et la valeur. J'imprime la valeur et l'élément suivant de la liste ex_list[i+1]
. Le si i < len(list) - 1
signifie que v est et non le dernier membre de la liste. Si c'est: Imprimer v et le premier élément de la liste print v, ex_list[0]
.
Edit:
vous pouvez lui faire retourner une liste. Il suffit d'ajouter les tuples imprimés à une liste et de le retourner.
def pairs(ex_list):
result = []
for i, v in enumerate(ex_list):
if i < len(list) - 1:
result.append((v, ex_list[i+1]))
else:
result.append((v, ex_list[0]))
return result
bien sûr, vous pouvez toujours utiliser un deque :
from collections import deque
from itertools import *
def pairs(lst, n=2):
itlst = iter(lst)
start = list(islice(itlst, 0, n-1))
deq = deque(start, n)
for elt in chain(itlst, start):
deq.append(elt)
yield list(deq)
i=(range(10))
for x in len(i):
print i[:2]
i=i[1:]+[i[1]]
plus pythonique que cela est impossible