Quelle est la façon la plus «pythonique» d'itérer une liste en morceaux?

488

J'ai un script Python qui prend en entrée une liste d'entiers, dont j'ai besoin pour travailler avec quatre entiers à la fois. Malheureusement, je n'ai pas le contrôle de l'entrée, ou je la ferais passer sous forme de liste de tuples à quatre éléments. Actuellement, je répète de cette façon:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

Cela ressemble beaucoup à "C-think", ce qui me fait soupçonner qu'il existe une façon plus pythonique de gérer cette situation. La liste est supprimée après l'itération, il n'est donc pas nécessaire de la conserver. Peut-être que quelque chose comme ça serait mieux?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

Mais ne se sent pas tout à fait bien. : - /

Question connexe: comment diviser une liste en morceaux de taille égale en Python?

Ben Blank
la source
3
Votre code ne fonctionne pas si la taille de la liste n'est pas un multiple de quatre.
Pedro Henriques
5
J'étends () la liste de façon à ce que sa longueur soit un multiple de quatre avant d'arriver aussi loin.
Ben Blank
4
@ ΤΖΩΤΖΙΟΥ - Les questions sont très similaires, mais pas tout à fait en double. C'est "divisé en n'importe quel nombre de morceaux de taille N" vs "divisé en N morceaux de n'importe quelle taille". :-)
Ben Blank

Réponses:

340

Modifié à partir de la section des recettes des documents itertools de Python :

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Exemple
En pseudocode pour garder l'exemple concis.

grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

Remarque: sur Python 2, utilisez izip_longestau lieu de zip_longest.

Craz
la source
67
J'ai enfin eu la chance de jouer avec cela dans une session python. Pour ceux qui sont aussi confus que moi, cela alimente le même itérateur à izip_longest plusieurs fois, ce qui lui fait consommer des valeurs successives de la même séquence plutôt que des valeurs rayées de séquences distinctes. J'aime cela!
Ben Blank, le
6
Quelle est la meilleure façon de filtrer la valeur de remplissage? ([article pour article dans les articles si l'article n'est pas la valeur de remplissage] pour les articles dans le groupeur (itérable))?
gotgenes
14
Je soupçonne que les performances de cette recette de mérou pour des morceaux de 256 Ko seront très médiocres, car izip_longestelles seront alimentées en arguments de 256 Ko.
anatoly techtonik
13
À plusieurs endroits, les commentateurs disent "quand j'ai finalement compris comment cela fonctionnait ..." Peut-être qu'un peu d'explication est nécessaire. Notamment l'aspect liste des itérateurs.
LondonRob
6
Existe-t-il un moyen de l'utiliser sans Noneremplir le dernier morceau?
CMCDragonkai
420
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

Facile. Facile. Vite. Fonctionne avec n'importe quelle séquence:

text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']
nosklo
la source
16
La version de @Carlos Crasborn fonctionne pour tous les itérables (pas seulement les séquences comme le code ci-dessus); il est concis et probablement aussi rapide, voire plus rapide. Bien que cela puisse être un peu obscur (peu clair) pour les personnes qui ne connaissent pas le itertoolsmodule.
jfs
1
D'accord. C'est la manière la plus générique et pythonique. Clair et concis. (et fonctionne sur le moteur d'application)
Matt Williamson
3
Notez que chunkerrenvoie a generator. Remplacez le retour par: return [...]pour obtenir une liste.
Dror
11
Au lieu d'écrire un bâtiment de fonction, puis retourner un générateur, vous pouvez aussi écrire un générateur directement, en utilisant yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Je ne sais pas si en interne cela serait traité différemment dans tous les aspects pertinents, mais cela pourrait même être un peu plus clair.
Alfe
3
Notez que cela ne fonctionne que pour les séquences qui prennent en charge l'accès aux éléments par index et ne fonctionnera pas pour les itérateurs génériques, car ils peuvent ne pas prendre en charge la __getitem__méthode.
apollov
135

Je suis fan de

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size
S.Lott
la source
Comment se comporte-t-il si len (ints) n'est pas un multiple de chunkSize?
PlsWork
3
@AnnaVopureta chunkaura 1, 2 ou 3 éléments pour le dernier lot d'éléments. Consultez cette question pour savoir pourquoi les indices de tranche peuvent être hors limites .
Boris
22
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

Autrement:

import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4
Markus Jarderot
la source
2
+1 pour l'utilisation de générateurs, des coutures comme la plus "pythonique" de toutes les solutions suggérées
Sergey Golovchenko
7
C'est assez long et maladroit pour quelque chose d'aussi facile, qui n'est pas du tout très pythonique. Je préfère la version de S. Lott
zenazn
4
@zenazn: cela fonctionnera sur les instances de générateur, pas le découpage
Janus Troelsen
En plus de fonctionner correctement avec des générateurs et autres itérateurs non-tranchables, la première solution ne nécessite pas non plus de valeur de "remplissage" si le morceau final est plus petit que size, ce qui est parfois souhaitable.
dano
1
+1 également pour les générateurs. D'autres solutions nécessitent un lenappel et ne fonctionnent donc pas sur d'autres générateurs.
Cuadue
12
from itertools import izip_longest

def chunker(iterable, chunksize, filler):
    return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
Pedro Henriques
la source
Une manière lisible de le faire est stackoverflow.com/questions/434287/…
jfs
Notez qu'en python 3 izip_longestest remplacé parzip_longest
mdmjsh
11

La solution idéale pour ce problème fonctionne avec des itérateurs (pas seulement des séquences). Cela devrait également être rapide.

Voici la solution fournie par la documentation d'itertools:

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

Utiliser ipython %timeit sur mon mac book air, j'obtiens 47,5 us par boucle.

Cependant, cela ne fonctionne vraiment pas pour moi car les résultats sont rembourrés pour être des groupes de taille égale. Une solution sans rembourrage est légèrement plus compliquée. La solution la plus naïve pourrait être:

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

Simple, mais assez lent: 693 us par boucle

La meilleure solution que j'ai pu trouver avec des utilisations islicepour la boucle intérieure:

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

Avec le même ensemble de données, j'obtiens 305 us par boucle.

Impossible d'obtenir une solution pure plus rapidement que cela, je fournis la solution suivante avec une mise en garde importante: si vos données d'entrée contiennent des instances filldata, vous pouvez obtenir une mauvaise réponse.

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

Je n'aime vraiment pas cette réponse, mais elle est beaucoup plus rapide. 124 us par boucle

rhettg
la source
Vous pouvez réduire l' exécution de la recette # 3 par ~ 10-15% en déplaçant vers la couche C ( en omettant les itertoolsimportations, mapdoit être PY3 mapou imap): def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). Votre fonction finale peut être rendue moins fragile en utilisant une sentinelle: débarrassez-vous de l' fillvalueargument; ajoutez une première ligne fillvalue = object(), puis changez la ifcoche en if i[-1] is fillvalue:et la ligne qu'elle contrôle yield tuple(v for v in i if v is not fillvalue). Garantit qu'aucune valeur ne iterablepeut être confondue avec la valeur de remplissage.
ShadowRanger
BTW, bravo pour le # 4. J'étais sur le point de publier mon optimisation de # 3 comme une meilleure réponse (en termes de performances) que ce qui avait été publié jusqu'à présent, mais avec le réglage pour le rendre fiable et résilient, le # 4 fonctionne deux fois plus vite que l'optimisé # 3; Je ne m'attendais pas à une solution avec des boucles de niveau Python (et aucune différence algorithmique théorique AFAICT) pour gagner. Je suppose que le n ° 3 perd en raison des frais de construction / itération des isliceobjets (le n ° 3 gagne si nest relativement grand, par exemple, le nombre de groupes est petit, mais cela optimise pour un cas rare), mais je ne m'attendais pas à ce que ce soit tout à fait cela extrême.
ShadowRanger
Pour # 4, la première branche du conditionnel n'est prise que sur la dernière itération (le tuple final). Au lieu de reconstituer le tuple finale encore une fois, mettre en cache le modulo de la longueur de l'original itérables en haut et l' utiliser pour la tranche de rembourrage non désirée à partir izip_longestdu tuple final: yield i[:modulo]. En outre, pour la argsvariable tuple au lieu d'une liste: args = (iter(iterable),) * n. Rase encore quelques cycles d'horloge. Enfin, si nous ignorons fillvalue et supposons None, le conditionnel peut devenir if None in ipour encore plus de cycles d'horloge.
Kumba
1
@Kumba: Votre première suggestion suppose que l'entrée a une longueur connue. S'il s'agit d'un itérateur / générateur, et non d'une collection de longueur connue, il n'y a rien à mettre en cache. Il n'y a de toute façon aucune raison d'utiliser une telle optimisation; vous optimisez le cas rare (le dernier yield), tandis que le cas commun n'est pas affecté.
ShadowRanger
10

J'avais besoin d'une solution qui fonctionnerait également avec des ensembles et des générateurs. Je n'ai rien trouvé de très court et de joli, mais c'est tout à fait lisible.

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

Liste:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Ensemble:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Générateur:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
bcoughlan
la source
8

Semblable à d'autres propositions, mais pas exactement identiques, j'aime le faire de cette façon, car c'est simple et facile à lire:

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

De cette façon, vous n'obtiendrez pas le dernier morceau partiel. Si vous voulez obtenir (9, None, None, None)le dernier morceau, utilisez simplement izip_longestfrom itertools.

kriss
la source
peut être amélioré aveczip(*([it]*4))
Jean-François Fabre
@ Jean-François Fabre: d'un point de vue lisibilité je ne vois pas cela comme une amélioration. Et c'est aussi légèrement plus lent. C'est une amélioration si vous jouez au golf, ce que je ne suis pas.
kriss
non je ne joue pas au golf, mais si vous avez 10 arguments? J'ai lu cette construction dans une page officielle. Mais bien sûr, je n'arrive pas à la trouver en ce moment :)
Jean-François Fabre
@ Jean-François Fabre: si j'ai 10 arguments, ou un nombre variable d'arguments, c'est une option, mais je préfère écrire: zip (* (it,) * 10)
kriss
droite! c'est ce que j'ai lu. pas la liste des trucs que j'ai inventés :)
Jean-François Fabre
8

Si cela ne vous dérange pas d'utiliser un package externe, vous pouvez utiliser à iteration_utilities.grouperpartir de 1 . Il prend en charge tous les itérables (pas seulement les séquences):iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

qui imprime:

(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

Dans le cas où la longueur n'est pas un multiple de la taille du groupe, elle prend également en charge le remplissage (le dernier groupe incomplet) ou la troncature (suppression du dernier groupe incomplet) le dernier:

from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

Repères

J'ai également décidé de comparer le temps d'exécution de quelques-unes des approches mentionnées. Il s'agit d'un tracé log-log groupé en groupes d'éléments "10" basés sur une liste de taille variable. Pour des résultats qualitatifs: plus bas signifie plus vite:

entrez la description de l'image ici

Au moins dans cette référence, la iteration_utilities.groupermeilleure performance. Suivi de l'approche de Craz .

Le benchmark a été créé avec 1 . Le code utilisé pour exécuter ce benchmark était:simple_benchmark

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 Avertissement: je suis l'auteur des bibliothèques iteration_utilitieset simple_benchmark.

MSeifert
la source
7

Puisque personne ne l'a encore mentionné, voici une zip()solution:

>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

Cela ne fonctionne que si la longueur de votre séquence est toujours divisible par la taille du morceau ou si vous ne vous souciez pas d'un morceau de fin s'il ne l'est pas.

Exemple:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

Ou en utilisant itertools.izip pour renvoyer un itérateur au lieu d'une liste:

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

Le remplissage peut être corrigé en utilisant la réponse de @ ΤΖΩΤΖΙΟΥ :

>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)
jfs
la source
5

L'utilisation de map () au lieu de zip () corrige le problème de remplissage dans la réponse de JF Sebastian:

>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

Exemple:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
catwell
la source
2
Ceci est mieux géré avec itertools.izip_longest(Py2) / itertools.zip_longest(Py3); cette utilisation de mapest doublement obsolète et non disponible dans Py3 (vous ne pouvez pas passer Nonepour la fonction de mappeur, et elle s'arrête lorsque l'itérable le plus court est épuisé, pas le plus long; il ne se remplit pas).
ShadowRanger
4

Une autre approche consisterait à utiliser la forme à deux arguments de iter:

from itertools import islice

def group(it, size):
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

Cela peut être facilement adapté pour utiliser le rembourrage (cela est similaire à la réponse de Markus Jarderot ):

from itertools import islice, chain, repeat

def group_pad(it, size, pad=None):
    it = chain(iter(it), repeat(pad))
    return iter(lambda: tuple(islice(it, size)), (pad,) * size)

Ceux-ci peuvent même être combinés pour un rembourrage en option:

_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)
senderle
la source
1
préférable car vous avez la possibilité d'omettre le rembourrage!
n611x007
3

Si la liste est grande, la façon la plus performante de le faire sera d'utiliser un générateur:

def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
    print x

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)
Robert Rossney
la source
(Je pense que la suggestion d'itertools de MizardX est fonctionnellement équivalente à cela.)
Robert Rossney
1
(En fait, à la réflexion, non, je ne le fais pas. Itertools.islice retourne un itérateur, mais il n'en utilise pas un existant.)
Robert Rossney
C'est agréable et simple, mais pour une raison quelconque, même sans conversion en tuple 4 à 7 fois plus lent que la méthode de iterable = range(100000000)chunksize
groupage
Cependant, en général, je recommanderais cette méthode, car la méthode acceptée peut être extrêmement lente lors de la vérification du dernier élément: docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas
3

L'utilisation de petites fonctions et de choses ne m'intéresse pas vraiment; Je préfère simplement utiliser des tranches:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...
Volonté
la source
sympa mais pas bon pour un ruisseau indéfini qui n'a pas connu len. vous pouvez faire un test avec itertools.repeatou itertools.cycle.
n611x007
1
En outre, consomme de la mémoire en raison de l'utilisation d'une [...for...] compréhension de liste pour construire physiquement une liste au lieu d'utiliser une (...for...) expression de générateur qui se soucierait simplement de l'élément suivant et de la mémoire disponible
n611x007
2

Pour éviter toutes les conversions en liste import itertoolset:

>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

Produit:

... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

J'ai vérifié groupbyet il ne se convertit pas en liste ou n'utilise lendonc je pense que cela retardera la résolution de chaque valeur jusqu'à ce qu'elle soit réellement utilisée. Malheureusement, aucune des réponses disponibles (pour le moment) ne semblait offrir cette variation.

De toute évidence, si vous devez gérer chaque élément à son tour, imbriquez une boucle for g:

for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

Mon intérêt particulier pour cela était la nécessité de consommer un générateur pour soumettre des modifications par lots allant jusqu'à 1000 à l'API gmail:

    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)
John Mee
la source
Et si la liste que vous découpez est autre chose qu'une séquence d'entiers ascendants?
PaulMcG
@PaulMcGuire voir groupby ; étant donné une fonction pour décrire l'ordre, les éléments de l'itérable peuvent être n'importe quoi, non?
John Mee
1
Oui, je connais les groupes. Mais si les messages étaient les lettres "ABCDEFG", groupby(messages, lambda x: x/3)vous obtiendriez alors une TypeError (pour essayer de diviser une chaîne par un entier), pas des groupes de 3 lettres. Si vous le faisiez, groupby(enumerate(messages), lambda x: x[0]/3)vous pourriez avoir quelque chose. Mais vous ne l'avez pas dit dans votre message.
PaulMcG
2

Avec NumPy c'est simple:

ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

production:

1 2
3 4
5 6
7 8
endolith
la source
2
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()
Kamil Sindi
la source
2

Sauf si je manque quelque chose, la solution simple suivante avec des expressions de générateur n'a pas été mentionnée. Il suppose que la taille et le nombre de morceaux sont connus (ce qui est souvent le cas) et qu'aucun rembourrage n'est requis:

def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))
Alexey
la source
1

Dans votre deuxième méthode, je passerais au groupe suivant de 4 en faisant ceci:

ints = ints[4:]

Cependant, je n'ai fait aucune mesure de performance, donc je ne sais pas laquelle pourrait être la plus efficace.

Cela dit, je choisirais généralement la première méthode. Ce n'est pas joli, mais c'est souvent une conséquence de l'interfaçage avec le monde extérieur.

Greg Hewgill
la source
1

Encore une autre réponse, dont les avantages sont:

1) Facilement compréhensible
2) Fonctionne sur toutes les séquences itérables, pas seulement (certaines des réponses ci-dessus s'étoufferont dans les descripteurs de fichiers)
3) Ne charge pas le morceau en mémoire d'un seul coup
4) Ne fait pas une longue liste de références à le même itérateur en mémoire
5) Pas de remplissage des valeurs de remplissage à la fin de la liste

Cela étant dit, je ne l'ai pas chronométré, donc cela pourrait être plus lent que certaines des méthodes les plus intelligentes, et certains des avantages peuvent ne pas être pertinents compte tenu du cas d'utilisation.

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

Mise à jour:
quelques inconvénients dus au fait que les boucles internes et externes tirent des valeurs du même itérateur:
1) continuer ne fonctionne pas comme prévu dans la boucle externe - il continue simplement sur l'élément suivant plutôt que de sauter un morceau . Cependant, cela ne semble pas être un problème car il n'y a rien à tester dans la boucle externe.
2) la rupture ne fonctionne pas comme prévu dans la boucle intérieure - le contrôle se retrouvera à nouveau dans la boucle intérieure avec l'élément suivant dans l'itérateur. Pour sauter des morceaux entiers, enveloppez l'itérateur interne (ii ci-dessus) dans un tuple, par exemple for c in tuple(ii), ou définissez un drapeau et épuisez l'itérateur.

elhefe
la source
1
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
Wilfred Hughes
la source
+1, il omet le remplissage; le vôtre et bcoughlan « s est très similaire
n611x007
1

Vous pouvez utiliser la fonction partition ou chunks de la bibliothèque funcy :

from funcy import partition

for a, b, c, d in partition(4, ints):
    foo += a * b * c * d

Ces fonctions ont également des versions itérateur ipartitionetichunks , qui seront plus efficaces dans ce cas.

Vous pouvez également jeter un œil à leur mise en œuvre .

Suor
la source
1

À propos de la solution donnée par J.F. Sebastian ici :

def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

C'est intelligent, mais il a un inconvénient - renvoyez toujours le tuple. Comment obtenir une chaîne à la place?
Bien sûr, vous pouvez écrire''.join(chunker(...)) , mais le tuple temporaire est quand même construit.

Vous pouvez vous débarrasser du tuple temporaire en écrivant propre zip, comme ceci:

class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

alors

def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

Exemple d'utilisation:

>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'
GingerPlusPlus
la source
2
Pas une critique destinée à changer votre réponse, mais plutôt un commentaire: le code est une responsabilité. Plus vous écrivez de code, plus vous créez d'espace pour masquer les bogues. De ce point de vue, réécrire zipau lieu d'utiliser l'existant ne semble pas être la meilleure idée.
Alfe
1

J'aime cette approche. Il semble simple et non magique et prend en charge tous les types itérables et ne nécessite pas d'importations.

def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk
BallpointBen
la source
1

Je ne veux jamais que mes morceaux soient rembourrés, donc cette exigence est essentielle. Je trouve que la capacité de travailler sur n'importe quel itérable est également requise. Compte tenu de cela, j'ai décidé d'étendre la réponse acceptée, https://stackoverflow.com/a/434411/1074659 .

Les performances prennent un léger coup dans cette approche si le remplissage n'est pas souhaité en raison de la nécessité de comparer et de filtrer les valeurs rembourrées. Cependant, pour les gros morceaux, cet utilitaire est très performant.

#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks
franc
la source
1

Voici un chunker sans importations qui prend en charge les générateurs:

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

Exemple d'utilisation:

>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]
Cuadue
la source
1

Avec Python 3.8, vous pouvez utiliser l'opérateur morse et itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 10)                                                         
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
kafran
la source
0

Il ne semble pas y avoir une jolie façon de procéder. Voici une page qui contient un certain nombre de méthodes, notamment:

def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq
Harley Holcombe
la source
0

Si les listes sont de la même taille, vous pouvez les combiner en listes de 4 tuples avec zip(). Par exemple:

# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

Voici ce que la zip()fonction produit:

>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

Si les listes sont grandes et que vous ne souhaitez pas les combiner en une liste plus grande, utilisez itertools.izip(), qui produit un itérateur, plutôt qu'une liste.

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...
Brian Clapper
la source
0

Solution adhoc à une ligne pour parcourir une liste xen morceaux de taille 4-

for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
Tutul
la source