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.

3voto

Marcin Points 25366

Une mise en œuvre succincte est :

chunker = lambda iterable, n: (ifilterfalse(lambda x: x == (), chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=())))

Cela fonctionne parce que [iter(iterable)]*n est une liste contenant le même itérateur n fois ; le fait de passer dessus prend un élément de chaque itérateur dans la liste, qui est le même itérateur avec pour résultat que chaque élément de zip contient un groupe de n articles.

izip_longest est nécessaire pour consommer entièrement l'itérable sous-jacent, plutôt que d'arrêter l'itération lorsque le premier itérateur épuisé est atteint, ce qui supprime tout reste de l'itérable. iterable . Il est donc nécessaire de filtrer la valeur de remplissage. Une implémentation un peu plus robuste serait donc :

def chunker(iterable, n):
    class Filler(object): pass
    return (ifilterfalse(lambda x: x is Filler, chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=Filler)))

Cela garantit que la valeur de remplissage n'est jamais un élément dans l'itérable sous-jacent. En utilisant la définition ci-dessus :

iterable = range(1,11)

map(tuple,chunker(iterable, 3))
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10,)]

map(tuple,chunker(iterable, 2))
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]

map(tuple,chunker(iterable, 4))
[(1, 2, 3, 4), (5, 6, 7, 8), (9, 10)]

Cette implémentation fait presque ce que vous voulez, mais elle a des problèmes :

def chunks(it, step):
  start = 0
  while True:
    end = start+step
    yield islice(it, start, end)
    start = end

(La différence est que, parce que islice ne lève pas StopIteration ou quoi que ce soit d'autre sur les appels qui vont au-delà de la fin de l'option it ce rendement sera éternel ; il y a aussi la question un peu délicate que la islice les résultats doivent être consommés avant que ce générateur soit itéré).

Pour générer la fenêtre mobile de manière fonctionnelle :

izip(count(0, step), count(step, step))

Donc ça devient :

(it[start:end] for (start,end) in izip(count(0, step), count(step, step)))

Mais, cela crée toujours un itérateur infini. Donc, vous avez besoin de takewhile (ou peut-être quelque chose d'autre serait mieux) pour le limiter :

chunk = lambda it, step: takewhile((lambda x: len(x) > 0), (it[start:end] for (start,end) in izip(count(0, step), count(step, step))))

g = chunk(range(1,11), 3)

tuple(g)
([1, 2, 3], [4, 5, 6], [7, 8, 9], [10])

0 votes

1. Le premier extrait de code contient la ligne start = end ce qui ne semble rien faire, puisque l'itération suivante de la boucle commencera par start = 0 . De plus, la boucle est infinie -- c'est while True sans aucune break . 2. Qu'est-ce que len dans le deuxième extrait de code ? 3. Toutes les autres implémentations ne fonctionnent que pour les séquences, pas pour les itérateurs généraux. 4. La vérification x is () s'appuie sur un détail d'implémentation de CPython. Par souci d'optimisation, le tuple vide n'est créé qu'une seule fois et réutilisé par la suite. Ceci n'est cependant pas garanti par les spécifications du langage, vous devez donc utiliser la méthode suivante x == () .

0 votes

5. La combinaison de count() y takewhile() est beaucoup plus facile à mettre en œuvre en utilisant range() .

0 votes

@SvenMarnach : J'ai modifié le code et le texte en réponse à certains de vos points. Une correction bien nécessaire.

2voto

jsbueno Points 22212

"Le plus simple est mieux que le plus complexe" - un générateur simple de quelques lignes peut faire l'affaire. Il suffit de le placer dans un module utilitaire ou autre :

def grouper (iterable, n):
    iterable = iter(iterable)
    count = 0
    group = []
    while True:
        try:
            group.append(next(iterable))
            count += 1
            if count % n == 0:
                yield group
                group = []
        except StopIteration:
            yield group
            break

1voto

Zachary Young Points 2148

J'ai oublié où j'ai trouvé l'inspiration pour ça. Je l'ai un peu modifié pour qu'il fonctionne avec les GUID des MSI dans le registre Windows :

def nslice(s, n, truncate=False, reverse=False):
    """Splits s into n-sized chunks, optionally reversing the chunks."""
    assert n > 0
    while len(s) >= n:
        if reverse: yield s[:n][::-1]
        else: yield s[:n]
        s = s[n:]
    if len(s) and not truncate:
        yield s

reverse ne s'applique pas à votre question, mais c'est quelque chose que j'utilise beaucoup avec cette fonction.

>>> [i for i in nslice([1,2,3,4,5,6,7], 3)]
[[1, 2, 3], [4, 5, 6], [7]]
>>> [i for i in nslice([1,2,3,4,5,6,7], 3, truncate=True)]
[[1, 2, 3], [4, 5, 6]]
>>> [i for i in nslice([1,2,3,4,5,6,7], 3, truncate=True, reverse=True)]
[[3, 2, 1], [6, 5, 4]]

1voto

Carlos Quintanilla Points 5371

Voilà.

def chunksiter(l, chunks):
    i,j,n = 0,0,0
    rl = []
    while n < len(l)/chunks:        
        rl.append(l[i:j+chunks])        
        i+=chunks
        j+=j+chunks        
        n+=1
    return iter(rl)

def chunksiter2(l, chunks):
    i,j,n = 0,0,0
    while n < len(l)/chunks:        
        yield l[i:j+chunks]
        i+=chunks
        j+=j+chunks        
        n+=1

Exemples :

for l in chunksiter([1,2,3,4,5,6,7,8],3):
    print(l)

[1, 2, 3]
[4, 5, 6]
[7, 8]

for l in chunksiter2([1,2,3,4,5,6,7,8],3):
    print(l)

[1, 2, 3]
[4, 5, 6]
[7, 8]

for l in chunksiter2([1,2,3,4,5,6,7,8],5):
    print(l)

[1, 2, 3, 4, 5]
[6, 7, 8]

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