2010-02-27 20 views
9

Estoy buscando una forma de "recorrer" un iterador de Python. Es decir, me gustaría envolver un iterador dado iter y page_size con otro iterador que devolvería los elementos de iter como una serie de "páginas". Cada página sería en sí misma un iterador con hasta page_size iteraciones.¿Cómo escribir un buscapersonas para los iteradores de Python?

miré a través de itertools y lo más cercano que vi es itertools.islice. De alguna manera, lo que me gustaría es lo opuesto a itertools.chain - en lugar de encadenar una serie de iteradores en un iterador, me gustaría dividir un iterador en una serie de iteradores más pequeños. Esperaba encontrar una función de búsqueda en itertools pero no pude encontrar una.

Se me ocurrió la siguiente clase de buscapersonas y demostración.

class pager(object): 
    """ 
    takes the iterable iter and page_size to create an iterator that "pages through" iter. That is, pager returns a series of page iterators, 
    each returning up to page_size items from iter. 
    """ 
    def __init__(self,iter, page_size): 
     self.iter = iter 
     self.page_size = page_size 
    def __iter__(self): 
     return self 
    def next(self): 
     # if self.iter has not been exhausted, return the next slice 
     # I'm using a technique from 
     # https://stackoverflow.com/questions/1264319/need-to-add-an-element-at-the-start-of-an-iterator-in-python 
     # to check for iterator completion by cloning self.iter into 3 copies: 
     # 1) self.iter gets advanced to the next page 
     # 2) peek is used to check on whether self.iter is done 
     # 3) iter_for_return is to create an independent page of the iterator to be used by caller of pager 
     self.iter, peek, iter_for_return = itertools.tee(self.iter, 3) 
     try: 
      next_v = next(peek) 
     except StopIteration: # catch the exception and then raise it 
      raise StopIteration 
     else: 
      # consume the page from the iterator so that the next page is up in the next iteration 
      # is there a better way to do this? 
      # 
      for i in itertools.islice(self.iter,self.page_size): pass 
      return itertools.islice(iter_for_return,self.page_size) 



iterator_size = 10 
page_size = 3 

my_pager = pager(xrange(iterator_size),page_size) 

# skip a page, then print out rest, and then show the first page 
page1 = my_pager.next() 

for page in my_pager: 
    for i in page: 
     print i 
    print "----" 

print "skipped first page: " , list(page1) 

estoy buscando algo de retroalimentación y tienen las siguientes preguntas:

  1. ¿Hay un localizador que ya están en itertools que sirve un buscapersonas que estoy pasando por alto?
  2. La clonación de self.iter 3 veces me parece kludgy. Un clon es comprobar si self.iter tiene más elementos. Decidí ir con a technique Alex Martelli suggested (sabiendo que él escribió de un wrapping technique). El segundo clon fue para permitir que la página devuelta sea independiente del iterador interno (self.iter). ¿Hay alguna manera de evitar hacer 3 clones?
  3. ¿Hay una manera mejor de lidiar con la excepción StopIteration al lado de atraparla y luego volver a subirla? Estoy tentado de no atraparlo y dejarlo burbujear.

¡Gracias! -Raymond

+1

relacionadas: http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python http://stackoverflow.com/questions/434287/what-is-the-most-pythonic-way-to-iterate-over-a-list-in-chunks http://stackoverflow.com/questions/1335392/iteration-over-list-slices http : //stackoverflow.com/questions/760753/iterate-over-a-python-sequence-in-multiples-of-n – jfs

Respuesta

4

¿Por qué no usa esto?

def grouper(page_size, iterable): 
    page= [] 
    for item in iterable: 
     page.append(item) 
     if len(page) == page_size: 
      yield page 
      page= [] 
    yield page 

"Cada página sería en sí misma un iterador con hasta el tamaño de la página" elementos. Cada página es una simple lista de elementos, que es iterable.Puede usar yield iter(page) para generar el iterador en lugar del objeto, pero no veo cómo eso mejora nada.

Se arroja un estándar StopIteration al final.

¿Qué más quieres?

+0

Gracias por responder mi pregunta y por brindarme una buena manera de pensar sobre cómo recorrer el iterador. Creo que hay un pequeño error - ¿Quisiste agregar el elemento a la página? - como en: def grouper (tamaño_página, iterable): página = [] para el elemento en iterable: if len (página) == tamaño_página: página de rendimiento page = [] otra cosa: page.append (punto) página de rendimiento –

+0

@raymondyee: en realidad, hay una manera mejor. Su versión alberga un gran. Intente y vea que omite un elemento. –

+0

@ S.Lott - sí, por supuesto, puse mi page.append (item) en el lugar equivocado. Gracias por la corrección. Todavía estoy aprendiendo sobre cuándo itertools puede ayudar y cuándo no es necesario. ¿Alguna guía para ofrecer? –

7

Mire grouper() en el itertools recipes.

+0

Gracias por señalar las recetas. Puedo ver el uso de mero porque es eficiente y adaptando la receta para que se comporte exactamente como mi Buscapersonas. Todavía tengo curiosidad por saber si el Buscapersonas, tal como está, tiene mucho mérito, o debería abandonarlo por un enfoque de mero. –

0

Basado en el puntero a la receta de itertools para mero(), se me ocurrió la siguiente adaptación de mero() para imitar Buscapersonas. Quería filtrar ningún resultado Ninguno y quería devolver un iterador en lugar de una tupla (aunque sospecho que puede haber poca ventaja en hacer esta conversión)

# based on http://docs.python.org/library/itertools.html#recipes 
def grouper2(n, iterable, fillvalue=None): 
    args = [iter(iterable)] * n 
    for item in izip_longest(fillvalue=fillvalue, *args): 
     yield iter(filter(None,item)) 

daría la bienvenida a comentarios sobre cómo lo que pueda hacer para mejorar este código

2

lo haría así:

def pager(iterable, page_size): 
    args = [iter(iterable)] * page_size 
    fillvalue = object() 
    for group in izip_longest(fillvalue=fillvalue, *args): 
     yield (elem for elem in group if elem is not fillvalue) 

De esa manera, None puede ser un valor legítimo que el iterador escupe. Solo el único objeto fillvalue filtrado, y no es posible que sea un elemento del iterable.

+0

Gracias, Matt. Me hiciste darme cuenta de que no permitía que None fuera un valor legítimo del iterador y no contabilizaba el valor de la oferta. –

0
def group_by(iterable, size): 
    """Group an iterable into lists that don't exceed the size given. 

    >>> group_by([1,2,3,4,5], 2) 
    [[1, 2], [3, 4], [5]] 

    """ 
    sublist = [] 

    for index, item in enumerate(iterable): 
     if index > 0 and index % size == 0: 
      yield sublist 
      sublist = [] 

     sublist.append(item) 

    if sublist: 
     yield sublist 
Cuestiones relacionadas