155 votes

Itérer un itérateur par morceaux (de n) en Python ?

Pouvez-vous penser à un moyen agréable (peut-être avec itertools) de diviser un itérateur en morceaux de taille donnée ?

Par conséquent, l=[1,2,3,4,5,6,7] con chunks(l,3) devient un itérateur [1,2,3], [4,5,6], [7]

Je peux penser à un petit programme pour le faire mais pas à une manière agréable avec peut-être itertools.

3 votes

@kindall : C'est proche, mais pas identique, en raison de la gestion du dernier morceau.

5 votes

C'est légèrement différent, car cette question concernait les listes, et celle-ci est plus générale, les itérateurs. Bien que la réponse semble être la même au final.

0 votes

@recursive : Oui, après avoir lu le fil de discussion lié complètement, j'ai trouvé que tout dans ma réponse apparaît déjà quelque part dans l'autre fil de discussion.

182voto

Sven Marnach Points 133943

Le site grouper() recette du itertools de la documentation recettes se rapproche de ce que vous voulez :

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

Il remplira le dernier morceau avec une valeur de remplissage, cependant.

Une solution moins générale qui ne fonctionne que sur les séquences mais qui gère le dernier morceau comme souhaité est

[my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)]

Enfin, une solution qui fonctionne sur des itérateurs généraux et se comporte comme souhaité est

def grouper(n, iterable):
    it = iter(iterable)
    while True:
        chunk = tuple(itertools.islice(it, n))
        if not chunk:
            return
        yield chunk

0 votes

Merci pour cette idée et toutes les autres ! Je suis désolé d'avoir manqué les nombreux fils de discussion qui traitent déjà de cette question. J'avais essayé islice mais je n'ai pas vu qu'il absorbait effectivement l'itérateur comme souhaité. Maintenant, je pense à définir une classe d'itérateur personnalisée qui fournit toutes sortes de fonctionnalités :)

0 votes

Serait-ce if chunk: yield chunk serait acceptable ? elle rase une ligne et est aussi sémantique qu'une simple return .

6 votes

@barraponto : Non, ce ne serait pas acceptable, puisque vous vous retrouveriez avec une boucle infinie.

94voto

reclosedev Points 5208

Bien que l'OP demande à la fonction de retourner des morceaux sous forme de liste ou de tuple, si vous avez besoin de retourner des itérateurs, alors Sven Marnach's peut être modifiée :

def grouper_it(n, iterable):
    it = iter(iterable)
    while True:
        chunk_it = itertools.islice(it, n)
        try:
            first_el = next(chunk_it)
        except StopIteration:
            return
        yield itertools.chain((first_el,), chunk_it)

Quelques points de repère : http://pastebin.com/YkKFvm8b

Elle ne sera légèrement plus efficace que si votre fonction itère sur les éléments de chaque chunk.

24 votes

Je suis arrivé presque exactement à cette conception aujourd'hui, après avoir trouvé la réponse dans la documentation (qui est la réponse acceptée, la plus votée ci-dessus) massivement inefficace. Lorsque vous regroupez des centaines de milliers ou des millions d'objets à la fois - ce qui est le cas lorsque vous avez le plus besoin de la segmentation - elle doit être très efficace. C'est la bonne réponse.

0 votes

C'est la meilleure solution.

5 votes

Cela ne va-t-il pas se comporter de manière incorrecte si l'appelant n'épuise pas chunk_it (en interrompant la boucle interne plus tôt, par exemple) ?

17voto

Svein Lindal Points 149

Cela fonctionnera sur n'importe quel itérable. Elle renvoie le générateur de générateurs (pour une flexibilité totale). Je réalise maintenant que c'est fondamentalement la même chose que la solution de @reclosedevs, mais sans le superflu. Pas besoin de try...except comme le StopIteration se propage vers le haut, ce qui est ce que nous voulons.

Le site next(iterable) est nécessaire pour relever le StopIteration lorsque l'itérable est vide, puisque islice continuera à produire des générateurs vides pour toujours si vous le laissez faire.

C'est mieux parce qu'il ne fait que deux lignes, tout en étant facile à comprendre.

def grouper(iterable, n):
    while True:
        yield itertools.chain((next(iterable),), itertools.islice(iterable, n-1))

Notez que next(iterable) est mis dans un tuple. Dans le cas contraire, si next(iterable) était lui-même itérable, alors itertools.chain l'aplatirait. Merci à Jeremy Brown d'avoir signalé ce problème.

8voto

eidorb Points 56

Je travaillais sur quelque chose aujourd'hui et j'ai trouvé ce que je pense être une solution simple. Elle est similaire à jsbueno's réponse, mais je crois que la sienne serait vide group lorsque la longueur de iterable est divisible par n . Ma réponse effectue une simple vérification lorsque le iterable est épuisé.

def chunk(iterable, chunk_size):
    """Generate sequences of `chunk_size` elements from `iterable`."""
    iterable = iter(iterable)
    while True:
        chunk = []
        try:
            for _ in range(chunk_size):
                chunk.append(iterable.next())
            yield chunk
        except StopIteration:
            if chunk:
                yield chunk
            break

3voto

Peter Otten Points 31

En voici un qui renvoie des morceaux paresseux ; utilisez map(list, chunks(...)) si vous voulez des listes.

from itertools import islice, chain
from collections import deque

def chunks(items, n):
    items = iter(items)
    for first in items:
        chunk = chain((first,), islice(items, n-1))
        yield chunk
        deque(chunk, 0)

if __name__ == "__main__":
    for chunk in map(list, chunks(range(10), 3)):
        print chunk

    for i, chunk in enumerate(chunks(range(10), 3)):
        if i % 2 == 1:
            print "chunk #%d: %s" % (i, list(chunk))
        else:
            print "skipping #%d" % i

0 votes

Vous pouvez commenter comment cela fonctionne.

3 votes

Un avertissement : ce générateur produit des itérables qui ne restent valides que jusqu'à ce que l'itérable suivant soit demandé. Lorsque vous utilisez par exemple list(chunks(range(10), 3)) tous les itérables auront déjà été consommés.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X