2012-06-22 29 views
6

escribiendo una función general que puede iterar sobre cualquier iterable return now, next pairs.python ahora, siguiente, n iteración

def now_nxt(iterable): 
    iterator = iter(iterable) 
    nxt = iterator.__next__() 
    for x in iterator: 
     now = nxt 
     nxt = x 
     yield (now,nxt) 

for i in now_nxt("hello world"): 
    print(i) 

('h', 'e') 
('e', 'l') 
('l', 'l') 
('l', 'o') 
('o', ' ') 
(' ', 'w') 
('w', 'o') 
('o', 'r') 
('r', 'l') 
('l', 'd') 

He estado pensando en la mejor manera de escribir una función donde se puede establecer el número de elementos en cada tupla.

por ejemplo, si era

func("hello",n=3) 

el resultado sería:

('h','e','l') 
('e','l','l') 
('l','l','o') 

Soy nuevo en el uso timeit, así que por favor señalar si haciendo nada malo aquí:

import timeit 

def n1(iterable, n=1): 
    #now_nxt_deque 
    from collections import deque 
    deq = deque(maxlen=n) 
    for i in iterable: 
     deq.append(i) 
     if len(deq) == n: 
      yield tuple(deq) 

def n2(sequence, n=2): 
    # now_next 
    from itertools import tee 
    iterators = tee(iter(sequence), n) 
    for i, iterator in enumerate(iterators): 
     for j in range(i): 
      iterator.__next__() 
    return zip(*iterators) 

def n3(gen, n=2): 
    from itertools import tee, islice 
    gens = tee(gen, n) 
    gens = list(gens) 
    for i, gen in enumerate(gens): 
     gens[i] = islice(gens[i], i, None) 
    return zip(*gens) 


def prin(func): 
    for x in func: 
     yield x 

string = "Lorem ipsum tellivizzle for sure ghetto, consectetuer adipiscing elit." 

print("func 1: %f" %timeit.Timer("prin(n1(string, 5))", "from __main__ import n1, string, prin").timeit(100000)) 
print("func 2: %f" %timeit.Timer("prin(n2(string, 5))", "from __main__ import n2, string, prin").timeit(100000)) 
print("func 3: %f" %timeit.Timer("prin(n3(string, 5))", "from __main__ import n3, string, prin").timeit(100000)) 

resultados:

$ py time_this_function.py 
func 1: 0.163129 
func 2: 2.383288 
func 3: 1.908363 
+0

Probablemente no :) –

+0

ve bien para mí. Podría intentar deshacerme del control len() teniendo dos bucles: uno para preparar el deque con los primeros n-1 elementos, y luego un bucle para producir tuplas completas. Pero también podría decidir que era mejor con solo un ciclo. –

+1

Es posible que desee considerar simplemente hacer la pregunta "cómo hacer esto", y luego publicar su cosa como una respuesta, en lugar de ponerlo en la pregunta. –

Respuesta

5

Mi propuesta sería,

from collections import deque 

def now_nxt_deque(iterable, n=1): 
    deq = deque(maxlen=n) 
    for i in iterable: 
     deq.append(i) 
     if len(deq) == n: 
      yield tuple(deq) 

for i in now_nxt_deque("hello world", 3): 
    print(i) 

('h', 'e', 'l') 
('e', 'l', 'l') 
('l', 'l', 'o') 
('l', 'o', ' ') 
('o', ' ', 'w') 
(' ', 'w', 'o') 
('w', 'o', 'r') 
('o', 'r', 'l') 
('r', 'l', 'd') 
+0

+1: ¡Esta es una solución extremadamente eficiente! – jathanism

2

Mi solución:

def nn(itr, n): 
    iterable = iter(itr) 

    last = tuple(next(iterable, None) for _ in xrange(n)) 
    yield last 
    for _ in xrange(len(itr)): 
     last = tuple(chain(last[1:], [next(iterable)])) 
     yield last 

Esto se hizo para Python 2, si quieres usarlo con Python 3, reemplazar xrange con range.

next, tiene una gran default parámetro, el cual será devuelto en lugar de elevar un StopIteration, también se puede añadir este parámetro por defecto a la función de este modo:

def nn(itr, n, default=None): 
    iterable = iter(itr) 

    last = tuple(next(iterable, default) for _ in xrange(n)) 
    yield last 
    for _ in xrange(len(itr)): 
     last = tuple(chain(last[1:], [next(iterable, default)])) 
     yield last 

jugué un poco más con ella, p.ej usando itr.__class__() por defecto, pero eso parece incorrecto para las listas y tuplas, así que tiene sentido para las cadenas.

+0

Esta es definitivamente una solución compacta. –

+1

'len (itr)' no funciona para generadores, lo que hace que este propósito sea menos general. –

+0

Tienes razón, así que traté de encontrar una mejor manera y recordé la documentación de itertools, edité la publicación. – dav1d

5

Aquí está una manera muy sencilla de hacerlo:

  • Clon iterador n veces usando usando itertools.tee
  • Avance del i º iterador i veces
  • izip todos ellos juntos
import itertools 

def now_next(sequence, n=2): 
    iterators = itertools.tee(iter(sequence), n) 
    for i, iterator in enumerate(iterators): 
     for j in range(i): 
      iterator.next() 
    return itertools.izip(*iterators) 
+0

¡Gran solución! Solo un pensamiento: ya que ha comenzado con iteradores, tal vez tendría sentido seguir con ese patrón y 'devolver itertools.izip (* iterators)' al final? – jathanism

+0

@jathanism: buen punto. Acabo de terminar con 'zip' para mayor claridad. Editado – Eric

+0

@Eric, ¿cuán escalable sería este método? ¿Hay mucho costo involucrado con la clonación de iteradores? – beoliver

1

Una variación de la técnica de Eric que utiliza rebanar

from itertools import tee, islice, izip 

def now_next(gen, n=2): 
    gens = tee(gen, n) 
    gens = list(gens) 
    for i, gen in enumerate(gens): 
    gens[i] = islice(gens[i], i, None) 
    return izip(*gens) 

for x in now_next((1,2,3,4,5,6,7)): 
    print x 
+0

esto parece ser más rápido que Erics, he agregado la función a la lista de tiempos en la pregunta. - tenga en cuenta que como estoy usando Python 3.X, izip ahora es zip – beoliver

+0

[Puedo llevarlo a una línea] (http://stackoverflow.com/a/11167811/102441) – Eric

0

Una sola línea sobre la base de la respuesta de cravoori:

from itertools import tee, islice, izip 

def now_next(gen, n=2): 
    return izip(*(islice(g, i, None) for i, g in enumerate(tee(gen, n)))) 
Cuestiones relacionadas