Iterar sobre pares en una lista (moda circular) en Python

El problema es fácil, quiero recorrer cada elemento de la lista y el siguiente en pares (envolviendo el último con el primero).

He pensado en dos formas no pythonicas de hacerlo:

def pairs(lst): n = len(lst) for i in range(n): yield lst[i],lst[(i+1)%n] 

y:

 def pairs(lst): return zip(lst,lst[1:]+[lst[:1]]) 

Rendimiento esperado:

 >>> 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) >>> 

¿Alguna sugerencia sobre una forma más pythonica de hacer esto? tal vez hay una función predefinida por ahí que no he oído hablar?

También podría ser interesante una versión más general en n (con tripletes, cuartetos, etc. en lugar de pares).

 def pairs(lst): i = iter(lst) first = prev = item = i.next() for item in i: yield prev, item prev = item yield item, first 

Funciona en cualquier secuencia no vacía, no se requiere indexación.

Me he codificado las versiones generales de la tupla, me gusta la primera por su elegante simplicidad, cuanto más la miro, más Pythonic me parece … después de todo, ¿qué es más Pythonic que un liner con zip? , expansión de argumentos de asterisco, comprensión de listas, división de listas, concatenación de listas y “rango”?

 def ntuples(lst, n): return zip(*[lst[i:]+lst[:i] for i in range(n)]) 

La versión de itertools debería ser lo suficientemente eficiente incluso para listas grandes …

 from itertools import * def ntuples(lst, n): return izip(*[chain(islice(lst,i,None), islice(lst,None,i)) for i in range(n)]) 

Y una versión para secuencias no 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,) 

De todos modos, gracias a todos por vuestras sugerencias! 🙂

Yo, como siempre, como tee:

 from itertools import tee, izip, chain def pairs(iterable): a, b = tee(iterable) return izip(a, chain(b, [next(b)])) 

Esto podría ser satisfactorio:

 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 te gusta este tipo de cosas, mira los artículos de python en wordaligned.org . El autor tiene un especial amor por los generadores en python.

Lo haría así (principalmente porque puedo leer esto):

 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 

da:

 (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)] 

Reemplace el rango (10) con la lista que desee.

En general, la “indexación circular” es bastante fácil en python; Solo usa:

 a[i%len(a)] 

Para responder a su pregunta sobre la resolución del caso general:

 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 salida es así:

 >>> 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] 

Este ciclo infinito, para bien o para mal, pero es algorítmicamente muy claro.

 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() 

da:

 (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') 

Versión aún más corta de la solución zip * de Fortran (con lambda esta vez;):

 group = lambda t, n: zip(*[t[i::n] for i in range(n)]) group([1, 2, 3, 3], 2) 

da:

 [(1, 2), (3, 4)] 

Aquí hay una versión que admite un índice de inicio opcional (por ejemplo, para devolver (4, 0) como el primer par, use 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 devuelve una tupla con el número de índice y el valor. ex_list[i+1] el valor y el siguiente elemento de la lista ex_list[i+1] . Si i < len(list) - 1 significa que v no es el último miembro de la lista. Si es: print v y el primer elemento de la lista print v, ex_list[0] .

Editar:

Puedes hacer que te devuelva una lista. Simplemente agregue las tuplas impresas a una lista y devuélvalas.

 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 

Por supuesto, siempre puedes usar 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]] 

Más python que esto es imposible.