Comment divisez-vous une liste en morceaux de taille égale?


2269

J'ai une liste de longueur arbitraire, et je dois la diviser en morceaux de taille égale et l'utiliser. Il existe des moyens évidents de le faire, comme garder un compteur et deux listes, et lorsque la deuxième liste se remplit, ajoutez-la à la première liste et videz la deuxième liste pour la prochaine série de données, mais cela est potentiellement extrêmement coûteux.

Je me demandais si quelqu'un avait une bonne solution à cela pour des listes de n'importe quelle longueur, par exemple en utilisant des générateurs.

Je cherchais quelque chose d'utile itertoolsmais je n'ai rien trouvé d'utile. Ça aurait peut-être manqué ça.

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


1
Avant de publier une nouvelle réponse, considérez qu'il existe déjà plus de 60 réponses à cette question. Veuillez vous assurer que votre réponse fournit des informations qui ne figurent pas parmi les réponses existantes.
janniks

Pour les utilisateurs qui veulent éviter un fragment final arbitrairement petit, regardez sur Fractionner une liste en N parties de longueur approximativement égale
wim

Réponses:


3154

Voici un générateur qui produit les morceaux que vous souhaitez:

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

import pprint
pprint.pprint(list(chunks(range(10, 75), 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]]

Si vous utilisez Python 2, vous devez utiliser à la xrange()place de range():

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in xrange(0, len(lst), n):
        yield lst[i:i + n]

Vous pouvez également simplement utiliser la compréhension de liste au lieu d'écrire une fonction, bien que ce soit une bonne idée d'encapsuler des opérations comme celle-ci dans des fonctions nommées afin que votre code soit plus facile à comprendre. Python 3:

[lst[i:i + n] for i in range(0, len(lst), n)]

Version Python 2:

[lst[i:i + n] for i in xrange(0, len(lst), n)]

72
Que se passe-t-il si nous ne pouvons pas déterminer la longueur de la liste? Essayez ceci sur itertools.repeat ([1, 2, 3]), par exemple
jespern

47
C'est une extension intéressante de la question, mais la question d'origine posait clairement la question de fonctionner sur une liste.
Ned Batchelder

33
cette fonction doit être dans la fichue bibliothèque standard
dgan

6
@Calimo: que proposez-vous? Je vous remets une liste de 47 éléments. Comment aimeriez-vous le diviser en "morceaux de taille égale"? L'OP a accepté la réponse, ils sont donc clairement OK avec le dernier morceau de taille différente. Peut-être que la phrase anglaise est imprécise?
Ned Batchelder

8
Veuillez ne pas nommer vos variables l, elles ressemblent exactement à 1 et sont déroutantes. Les gens copient votre code et pensent que c'est ok.
Yasen

555

Si vous voulez quelque chose de super simple:

def chunks(l, n):
    n = max(1, n)
    return (l[i:i+n] for i in range(0, len(l), n))

Utiliser xrange()au lieu de range()dans le cas de Python 2.x


6
Ou (si nous faisons différentes représentations de cette fonction particulière), vous pouvez définir une fonction lambda via: lambda x, y: [x [i: i + y] pour i dans la plage (0, len (x), y) ]. J'adore cette méthode de compréhension de liste!
JP

4
après le retour, il doit y avoir [, pas (
alwbtc

2
"Super simple" signifie ne pas avoir à déboguer des boucles infinies - bravo pour le max().
Bob Stein

cette solution n'a rien de simple
mit

1
@Nhoj_Gonk Oups ce n'est pas une boucle infinie, mais des morceaux (L, 0) déclencheraient une ValueError sans le max (). Au lieu de cela, le max () transforme tout ce qui est inférieur à 1 en un 1.
Bob Stein

295

Directement à partir de la (vieille) documentation Python (recettes pour itertools):

from itertools import izip, chain, repeat

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

La version actuelle, comme suggéré par JFSebastian:

#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

Je suppose que la machine à remonter le temps de Guido fonctionne - a fonctionné - fonctionnera - aura fonctionné - fonctionnait à nouveau.

Ces solutions fonctionnent car [iter(iterable)]*n(ou l'équivalent dans la version précédente) crée un itérateur, répété plusieurs nfois dans la liste. izip_longesteffectue ensuite un round-robin de "chaque" itérateur; comme il s'agit du même itérateur, il est avancé par chacun de ces appels, ce qui fait que chaque zip-roundrobin génère un tuple d' néléments.


@ninjagecko: list(grouper(3, range(10)))retourne [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]et tous les tuples sont de longueur 3. Veuillez développer votre commentaire car je ne le comprends pas; comment appelez-vous une chose et comment la définissez-vous comme étant un multiple de 3 en «s'attendant à ce que votre chose soit un multiple de 3»? Merci d'avance.
tzot le

14
a voté pour cela car il fonctionne sur des générateurs (pas de len) et utilise le module itertools généralement plus rapide.
Michael Dillon

88
Un exemple classique d' itertoolsapproche fonctionnelle sophistiquée qui révèle des boues illisibles, par rapport à une implémentation pure et simple de python pur
wim

15
@wim Étant donné que cette réponse a commencé comme un extrait de la documentation Python, je vous suggère d'ouvrir un problème sur bugs.python.org .
tzot

1
@pedrosaurio si l==[1, 2, 3]alors f(*l)est équivalent à f(1, 2, 3). Voir cette question et la documentation officielle .
tzot

227

Je sais que c'est un peu vieux mais personne n'a encore mentionné numpy.array_split:

import numpy as np

lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
#  array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
#  array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
#  array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
#  array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]

12
Cela vous permet de définir le nombre total de morceaux, pas le nombre d'éléments par morceau.
FizxMike

6
vous pouvez faire le calcul vous-même. si vous avez 10 éléments, vous pouvez les regrouper en blocs de 2, 5 éléments ou cinq blocs de 2 éléments
Moj

24
+1 C'est ma solution préférée, car elle divise le tableau en tableaux de taille égale , contrairement à d'autres solutions (dans toutes les autres solutions que j'ai examinées, le dernier tableau peut être arbitrairement petit).
MiniQuark

@MiniQuark mais qu'est-ce que cela fait lorsque le nombre de blocs n'est pas un facteur de la taille d'origine du tableau?
Baldrickk

1
@Baldrickk Si vous divisez N éléments en K morceaux, alors les premiers N% K morceaux auront N // K + 1 éléments, et le reste aura N // K éléments. Par exemple, si vous divisez un tableau contenant 108 éléments en 5 morceaux, les 108% 5 = 3 premiers morceaux contiendront 108 // 5 + 1 = 22 éléments, et le reste des morceaux aura 108 // 5 = 21 éléments.
MiniQuark

147

Je suis surpris que personne n'ait pensé à utiliser iterla forme à deux arguments de :

from itertools import islice

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

Démo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]

Cela fonctionne avec n'importe quel itérable et produit une sortie paresseusement. Il renvoie des tuples plutôt que des itérateurs, mais je pense néanmoins qu'il a une certaine élégance. Il ne rembourre pas non plus; si vous voulez du rembourrage, une simple variation de ce qui précède suffira:

from itertools import islice, chain, repeat

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

Démo:

>>> list(chunk_pad(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk_pad(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Comme les izip_longestsolutions basées sur ce qui précède, les tampons ci-dessus sont toujours . Pour autant que je sache, il n'y a pas de recette itertools à une ou deux lignes pour une fonction qui optionnellement pad. En combinant les deux approches ci-dessus, celle-ci se rapproche assez:

_no_padding = object()

def chunk(it, size, padval=_no_padding):
    if padval == _no_padding:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(padval))
        sentinel = (padval,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

Démo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
>>> list(chunk(range(14), 3, None))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Je pense que c'est le segment le plus court proposé qui offre un rembourrage en option.

Comme l'a observé Tomasz Gandor , les deux blocs de remplissage s'arrêteront de façon inattendue s'ils rencontrent une longue séquence de valeurs de tampon. Voici une dernière variante qui contourne ce problème de manière raisonnable:

_no_padding = object()
def chunk(it, size, padval=_no_padding):
    it = iter(it)
    chunker = iter(lambda: tuple(islice(it, size)), ())
    if padval == _no_padding:
        yield from chunker
    else:
        for ch in chunker:
            yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))

Démo:

>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]

7
Magnifique, ta version simple est ma préférée. D'autres ont également proposé l' islice(it, size)expression de base et l'ont intégrée (comme je l'avais fait) dans une construction en boucle. Vous seul avez pensé à la version à deux arguments de iter()(je l'ignorais complètement), ce qui la rend super élégante (et probablement la plus efficace en termes de performances). Je ne savais pas que le premier argument à iterchanger pour une fonction à 0 argument quand on donnait la sentinelle. Vous renvoyez un itérateur (pot. Infini) de morceaux, pouvez utiliser un itérateur (pot. Infini) en entrée, vous n'avez ni len()tranche ni tableau. Impressionnant!
ThomasH

1
C'est pourquoi j'ai lu les réponses plutôt que de scanner uniquement le couple supérieur. Le remplissage facultatif était une exigence dans mon cas, et moi aussi j'ai appris la forme à deux arguments de l'iter.
Kerr

J'ai voté pour cela, mais quand même - ne la surévaluons pas! Tout d'abord, lambda peut être mauvais (fermeture lente sur l' ititérateur. Deuxièmement, et surtout, vous vous terminerez prématurément si un morceau de padvalexiste réellement dans votre itérable et doit être traité.
Tomasz Gandor

@TomaszGandor, je prends votre premier point! Bien que je comprenne que lambda n'est pas plus lent qu'une fonction ordinaire, bien sûr, vous avez raison de dire que l'appel de fonction et la recherche de fermeture ralentiront cela. Je ne sais pas quel serait le résultat relatif de cette performance par rapport à l' izip_longestapproche, par exemple - je pense que cela pourrait être un compromis complexe. Mais ... le padvalproblème n'est-il pas partagé par toutes les réponses qui proposent un padvalparamètre?
senderle

1
@TomaszGandor, c'est juste! Mais il n'a pas été trop difficile de créer une version qui corrige cela. ( En outre, notez que la première version, qui utilise ()comme sentinelle, fait correctement Ce travail est dû au fait. Les tuple(islice(it, size))rendements ()quand itest vide.)
senderle

93

Voici un générateur qui fonctionne sur des itérables arbitraires:

def split_seq(iterable, size):
    it = iter(iterable)
    item = list(itertools.islice(it, size))
    while item:
        yield item
        item = list(itertools.islice(it, size))

Exemple:

>>> import pprint
>>> pprint.pprint(list(split_seq(xrange(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

52
def chunk(input, size):
    return map(None, *([iter(input)] * size))

map(None, iter)est égal izip_longest(iter).
Thomas Ahle

1
@TomaszWysocki Pouvez-vous expliquer le *tuple d'itérateur devant vous? Peut-être dans votre texte de réponse, mais j'ai remarqué que cela était *utilisé de cette façon en Python auparavant. Merci!
theJollySin

1
@theJollySin Dans ce contexte, il est appelé l'opérateur splat. Son utilisation est expliquée ici - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms

2
Fermez mais le dernier morceau n'a aucun élément pour le remplir. Cela peut ou non être un défaut. Motif vraiment cool cependant.

49

Simple mais élégant

l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]

ou si vous préférez:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)

18
Tu ne doubleras pas une variable à la ressemblance d'un nombre arabe. Dans certaines polices, 1il lest impossible de les distinguer. Tout comme 0et O. Et parfois même Iet 1.
Alfe

14
@Alfe Polices défectueuses. Les gens ne devraient pas utiliser de telles polices. Pas pour la programmation, pas pour rien .
Jerry B

17
Les lambdas sont destinés à être utilisés en tant que fonctions sans nom. Il est inutile de les utiliser comme ça. De plus, cela rend le débogage plus difficile car le traceback rapportera "in <lambda>" au lieu de "in chunks" en cas d'erreur. Je vous souhaite bonne chance pour trouver un problème si vous en avez plein :) :)
Chris Koston

1
il devrait être 0 et non 1 à l'intérieur de xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta

REMARQUE: pour les utilisateurs de Python 3, utilisez range.
Christian Dean

40

Critique des autres réponses ici:

Aucune de ces réponses n'est des morceaux de taille égale, ils laissent tous un morceau de runt à la fin, donc ils ne sont pas complètement équilibrés. Si vous utilisiez ces fonctions pour distribuer le travail, vous avez intégré la perspective que l'une finisse probablement bien avant les autres, donc elle ne ferait rien pendant que les autres continueraient à travailler dur.

Par exemple, la première réponse actuelle se termine par:

[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]

Je déteste juste ce runt à la fin!

D' autres, comme list(grouper(3, xrange(7))), et à la chunk(xrange(7), 3)fois: retour [(0, 1, 2), (3, 4, 5), (6, None, None)]. Ce Nonene sont que du rembourrage, et plutôt inélégant à mon avis. Ils ne coupent PAS uniformément les itérables.

Pourquoi ne pouvons-nous pas mieux les répartir?

Ma (mes) solution (s)

Voici une solution équilibrée, adaptée d'une fonction que j'ai utilisée en production (Note en Python 3 à remplacer xrangepar range):

def baskets_from(items, maxbaskets=25):
    baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range
    for i, item in enumerate(items):
        baskets[i % maxbaskets].append(item)
    return filter(None, baskets) 

Et j'ai créé un générateur qui fait de même si vous le mettez dans une liste:

def iter_baskets_from(items, maxbaskets=3):
    '''generates evenly balanced baskets from indexable iterable'''
    item_count = len(items)
    baskets = min(item_count, maxbaskets)
    for x_i in xrange(baskets):
        yield [items[y_i] for y_i in xrange(x_i, item_count, baskets)]

Et enfin, puisque je vois que toutes les fonctions ci-dessus renvoient des éléments dans un ordre contigu (comme ils ont été donnés):

def iter_baskets_contiguous(items, maxbaskets=3, item_count=None):
    '''
    generates balanced baskets from iterable, contiguous contents
    provide item_count if providing a iterator that doesn't support len()
    '''
    item_count = item_count or len(items)
    baskets = min(item_count, maxbaskets)
    items = iter(items)
    floor = item_count // baskets 
    ceiling = floor + 1
    stepdown = item_count % baskets
    for x_i in xrange(baskets):
        length = ceiling if x_i < stepdown else floor
        yield [items.next() for _ in xrange(length)]

Production

Pour les tester:

print(baskets_from(xrange(6), 8))
print(list(iter_baskets_from(xrange(6), 8)))
print(list(iter_baskets_contiguous(xrange(6), 8)))
print(baskets_from(xrange(22), 8))
print(list(iter_baskets_from(xrange(22), 8)))
print(list(iter_baskets_contiguous(xrange(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(xrange(26), 5))
print(list(iter_baskets_from(xrange(26), 5)))
print(list(iter_baskets_contiguous(xrange(26), 5)))

Qui imprime:

[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'B', 'C'], ['D', 'E'], ['F', 'G']]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Notez que le générateur contigu fournit des morceaux dans les mêmes modèles de longueur que les deux autres, mais les éléments sont tous en ordre et ils sont aussi uniformément divisés que l'on peut diviser une liste d'éléments discrets.


Vous dites qu'aucun des éléments ci-dessus ne fournit des morceaux de taille égale. Mais celui-ci le fait, tout comme celui-ci .
senderle

1
@senderle, La première, list(grouper(3, xrange(7)))et la seconde, à la chunk(xrange(7), 3)fois le retour: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Ce Nonene sont que du rembourrage, et plutôt inélégant à mon avis. Ils ne coupent PAS uniformément les itérables. Merci pour votre vote!
Aaron Hall

4
Vous vous posez la question (sans le faire explicitement, donc je le fais maintenant ici) si des morceaux de taille égale (sauf le dernier, si ce n'est pas possible) ou si un résultat équilibré (aussi bon que possible) est plus souvent ce qui sera nécessaire. Vous supposez que la solution équilibrée est de préférer; cela peut être vrai si ce que vous programmez est proche du monde réel (par exemple un algorithme de distribution de cartes pour un jeu de cartes simulé). Dans d'autres cas (comme remplir des lignes avec des mots), on préfèrera garder les lignes aussi complètes que possible. Je ne peux donc pas vraiment préférer l'un à l'autre; ils sont juste pour différents cas d'utilisation.
Alfe

@ ChristopherBarrington-Leigh Bon point, pour DataFrames, vous devriez probablement utiliser des tranches, car je crois que les objets DataFrame ne copient généralement pas lors du découpage, par exempleimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall

1
@AaronHall Oups. J'ai supprimé mon commentaire parce que j'ai deviné ma critique, mais vous avez été rapide sur le tirage. Merci! En fait, mon affirmation selon laquelle cela ne fonctionne pas pour les trames de données est vraie. Si les articles sont une trame de données, utilisez simplement les éléments de rendement [plage (x_i, item_count, paniers)] comme dernière ligne. J'ai proposé une réponse distincte (encore une autre), dans laquelle vous spécifiez la taille de groupe souhaitée (minimum).
CPBL du

38

J'ai vu la réponse la plus impressionnante de Python dans un double de cette question:

from itertools import zip_longest

a = range(1, 16)
i = iter(a)
r = list(zip_longest(i, i, i))
>>> print(r)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

Vous pouvez créer n-tuple pour n'importe quel n. Si a = range(1, 15), alors le résultat sera:

[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]

Si la liste est divisée également, vous pouvez la remplacer zip_longestpar zip, sinon le triplet (13, 14, None)serait perdu. Python 3 est utilisé ci-dessus. Pour Python 2, utilisez izip_longest.


c'est bien si votre liste et vos morceaux sont courts, comment pourriez-vous adapter cela pour diviser votre liste en morceaux de 1000? vous n'allez pas coder zip (i, i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith

9
zip(i, i, i, ... i)avec des arguments "chunk_size" à zip () peut être écrit comme zip(*[i]*chunk_size)si c'est une bonne idée ou non est discutable, bien sûr.
Wilson F

1
L'inconvénient est que si vous ne divisez pas également, vous supprimerez des éléments, car le zip s'arrête au plus court itérable - & izip_longest ajouterait des éléments par défaut.
Aaron Hall

zip_longestdoit être utilisé, comme dans: stackoverflow.com/a/434411/1959808
Ioannis Filippidis

La réponse avec range(1, 15)manque déjà des éléments, car il y en a 14 range(1, 15), pas 15.
Ioannis Filippidis

35

Si vous connaissez la taille de la liste:

def SplitList(mylist, chunk_size):
    return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]

Si vous ne le faites pas (un itérateur):

def IterChunks(sequence, chunk_size):
    res = []
    for item in sequence:
        res.append(item)
        if len(res) >= chunk_size:
            yield res
            res = []
    if res:
        yield res  # yield the last, incomplete, portion

Dans ce dernier cas, il peut être reformulé d'une manière plus belle si vous pouvez être sûr que la séquence contient toujours un nombre entier de morceaux de taille donnée (c'est-à-dire qu'il n'y a pas de dernier morceau incomplet).


Je suis triste que cela soit enterré si loin. L'IterChunks fonctionne pour tout et est la solution générale et n'a aucune mise en garde que je sache.
Jason Dunkelberger

18

La bibliothèque toolz a partitionpour fonction:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]

Cela ressemble à la plus simple de toutes les suggestions. Je me demande simplement s'il peut être vrai que l'on doit utiliser une bibliothèque tierce pour obtenir une telle fonction de partition. Je m'attendais à ce que quelque chose d'équivalent avec cette fonction de partition existe en tant que langage intégré.
kasperd

1
vous pouvez faire une partition avec itertools. mais j'aime la bibliothèque toolz. c'est une bibliothèque d'inspiration clojure pour travailler sur des collections dans un style fonctionnel. vous n'obtenez pas l'immuabilité mais vous obtenez un petit vocabulaire pour travailler sur des collections simples. En plus, cytoolz est écrit en cython et obtient une belle amélioration des performances. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
zach

Le lien du commentaire de zach fonctionne si vous OMMISSIONS le slash: matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
mit


16

J'aime beaucoup la version du doc ​​Python proposée par tzot et JFSebastian, mais elle a deux défauts:

  • ce n'est pas très explicite
  • Je ne veux généralement pas de valeur de remplissage dans le dernier morceau

J'utilise beaucoup celui-ci dans mon code:

from itertools import islice

def chunks(n, iterable):
    iterable = iter(iterable)
    while True:
        yield tuple(islice(iterable, n)) or iterable.next()

MISE À JOUR: Une version de morceaux paresseux:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))

Quelle est la condition de rupture de la while Trueboucle?
wjandrea

@wjandrea: StopIterationlevé lorsque le tupleest vide et iterable.next()est exécuté. Cependant, cela ne fonctionne pas correctement dans Python moderne, où la sortie d'un générateur doit être effectuée returnsans augmenter StopIteration. Un try/except StopIteration: returnautour de la boucle entière (et en changeant iterable.next()pour next(iterable)pour la compatibilité entre versions) corrige cela avec un minimum de surcharge au moins.
ShadowRanger

15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Où AA est un tableau, SS est une taille de bloc. Par exemple:

>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3

2
c'est le meilleur et le plus simple.
F.Tamy

2
court et simple. simplicité sur complexité.
dkrynicki

15

J'étais curieux de connaître les performances des différentes approches et la voici:

Testé sur Python 3.5.1

import time
batch_size = 7
arr_len = 298937

#---------slice-------------

print("\r\nslice")
start = time.time()
arr = [i for i in range(0, arr_len)]
while True:
    if not arr:
        break

    tmp = arr[0:batch_size]
    arr = arr[batch_size:-1]
print(time.time() - start)

#-----------index-----------

print("\r\nindex")
arr = [i for i in range(0, arr_len)]
start = time.time()
for i in range(0, round(len(arr) / batch_size + 1)):
    tmp = arr[batch_size * i : batch_size * (i + 1)]
print(time.time() - start)

#----------batches 1------------

def batch(iterable, n=1):
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

print("\r\nbatches 1")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#----------batches 2------------

from itertools import islice, chain

def batch(iterable, size):
    sourceiter = iter(iterable)
    while True:
        batchiter = islice(sourceiter, size)
        yield chain([next(batchiter)], batchiter)


print("\r\nbatches 2")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#---------chunks-------------
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]
print("\r\nchunks")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in chunks(arr, batch_size):
    tmp = x
print(time.time() - start)

#-----------grouper-----------

from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(iterable, n, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

arr = [i for i in range(0, arr_len)]
print("\r\ngrouper")
start = time.time()
for x in grouper(arr, batch_size):
    tmp = x
print(time.time() - start)

Résultats:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844

3
l'analyse comparative à l'aide de la timebibliothèque n'est pas une bonne idée lorsque nous avons un timeitmodule
Azat Ibrakov

13

code:

def split_list(the_list, chunk_size):
    result_list = []
    while the_list:
        result_list.append(the_list[:chunk_size])
        the_list = the_list[chunk_size:]
    return result_list

a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print split_list(a_list, 3)

résultat:

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

12

Vous pouvez également utiliser la get_chunksfonction de utilspiebibliothèque comme:

>>> from utilspie import iterutils
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(iterutils.get_chunks(a, 5))
[[1, 2, 3, 4, 5], [6, 7, 8, 9]]

Vous pouvez installer utilspievia pip:

sudo pip install utilspie

Avertissement: je suis le créateur de la bibliothèque utilspie .


11

À ce stade, je pense que nous avons besoin d'un générateur récursif , juste au cas où ...

En python 2:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

En python 3:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    yield from chunks(li[n:], n)

De plus, en cas d'invasion extraterrestre massive, un générateur récursif décoré pourrait devenir pratique:

def dec(gen):
    def new_gen(li, n):
        for e in gen(li, n):
            if e == []:
                return
            yield e
    return new_gen

@dec
def chunks(li, n):
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

9

Avec les expressions d'affectation en Python 3.8, cela devient assez agréable:

import itertools

def batch(iterable, size):
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item

Cela fonctionne sur un itérable arbitraire, pas seulement une liste.

>>> import pprint
>>> pprint.pprint(list(batch(range(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

1
Maintenant, c'est une nouvelle réponse valable à cette question. En fait, j'aime bien ça. Je suis sceptique quant aux expressions d'affectation, mais quand elles fonctionnent, elles fonctionnent.
juanpa.arrivillaga

7

heh, une version en ligne

In [48]: chunk = lambda ulist, step:  map(lambda i: ulist[i:i+step],  xrange(0, len(ulist), step))

In [49]: chunk(range(1,100), 10)
Out[49]: 
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

36
Veuillez utiliser "def chunk" au lieu de "chunk = lambda". Cela fonctionne de la même manière. Une ligne. Mêmes caractéristiques. BEAUCOUP plus facile à lire et à comprendre pour le n00bz.
S.Lott

4
@ S.Lott: pas si les n00bz proviennent du schéma: P ce n'est pas un vrai problème. il y a même un mot-clé pour google! Quelles sont les autres fonctionnalités que nous évitons pour le bien du n00bz? Je suppose que le rendement n'est pas assez impératif / comme c pour être compatible avec le n00b non plus.
Janus Troelsen

16
L'objet fonction résultant de def chunkau lieu de chunk=lambdapossède .__ name__ attribut 'chunk' au lieu de '<lambda>'. Le nom spécifique est plus utile dans les retraits.
Terry Jan Reedy

1
@Alfe: Je ne sais pas si on pourrait l'appeler une différence sémantique principale, mais s'il y a un nom utile dans un retraçage au lieu de <lamba>ou non est, au moins, une différence notable.
martineau

1
Après avoir testé un tas d'entre eux pour les performances, c'est super!
Sunny Patel

7
def split_seq(seq, num_pieces):
    start = 0
    for i in xrange(num_pieces):
        stop = start + len(seq[i::num_pieces])
        yield seq[start:stop]
        start = stop

usage:

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for seq in split_seq(seq, 3):
    print seq

7

Une autre version plus explicite.

def chunkList(initialList, chunkSize):
    """
    This function chunks a list into sub lists 
    that have a length equals to chunkSize.

    Example:
    lst = [3, 4, 9, 7, 1, 1, 2, 3]
    print(chunkList(lst, 3)) 
    returns
    [[3, 4, 9], [7, 1, 1], [2, 3]]
    """
    finalList = []
    for i in range(0, len(initialList), chunkSize):
        finalList.append(initialList[i:i+chunkSize])
    return finalList

(12 septembre 2016) Cette réponse est la plus indépendante de la langue et la plus facile à lire.
D Adams

7

Sans appeler len (), ce qui est bon pour les grandes listes:

def splitter(l, n):
    i = 0
    chunk = l[:n]
    while chunk:
        yield chunk
        i += n
        chunk = l[i:i+n]

Et c'est pour les itérables:

def isplitter(l, n):
    l = iter(l)
    chunk = list(islice(l, n))
    while chunk:
        yield chunk
        chunk = list(islice(l, n))

La saveur fonctionnelle de ce qui précède:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(l))))

OU:

def chunks_gen_sentinel(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return iter(imap(tuple, continuous_slices).next,())

OU:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))

16
Il n'y a aucune raison d'éviter len()sur de grandes listes; c'est une opération à temps constant.
Thomas Wouters

7

Voici une liste d'approches supplémentaires:

Donné

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Code

La bibliothèque standard

list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

d = {}
for i, x in enumerate(iterable):
    d.setdefault(i//n, []).append(x)

list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
    dd[i//n].append(x)

list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

more_itertools+

list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]

list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

Références

+ Une bibliothèque tierce qui implémente les recettes itertools et plus encore.> pip install more_itertools


6

Voir cette référence

>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>> 

Python3


3
Bien, mais supprime des éléments à la fin si la taille ne correspond pas à un nombre entier de morceaux, par exemple, zip(*[iter(range(7))]*3)ne retourne [(0, 1, 2), (3, 4, 5)]et oublie que 6de l'entrée.
Alfe

6

Puisque tout le monde ici parle d'itérateurs. boltonsa une méthode parfaite pour cela, appelée iterutils.chunked_iter.

from boltons import iterutils

list(iterutils.chunked_iter(list(range(50)), 11))

Production:

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

Mais si vous ne voulez pas être miséricordieux sur la mémoire, vous pouvez utiliser l'ancienne méthode et stocker le plein listen premier lieu avec iterutils.chunked.


Et celui-ci fonctionne réellement quel que soit l'ordre, on regarde les sous-itérateurs !!
Peter Gerdes

6

Encore une solution

def make_chunks(data, chunk_size): 
    while data:
        chunk, data = data[:chunk_size], data[chunk_size:]
        yield chunk

>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
...     print chunk
... 
[1, 2]
[3, 4]
[5, 6]
[7]
>>> 

5
def chunks(iterable,n):
    """assumes n is an integer>0
    """
    iterable=iter(iterable)
    while True:
        result=[]
        for i in range(n):
            try:
                a=next(iterable)
            except StopIteration:
                break
            else:
                result.append(a)
        if result:
            yield result
        else:
            break

g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'

1
Bien que cela puisse ne pas sembler aussi court ou aussi joli que la plupart des réponses basées sur les outils, celle-ci fonctionne réellement si vous souhaitez imprimer la deuxième sous-liste avant d'accéder à la première, c'est-à-dire que vous pouvez définir i0 = next (g2); i1 = suivant (g2); et utilisez i1 avant d'utiliser i0 et ça ne casse pas !!
Peter Gerdes

5

Pensez à utiliser des morceaux de matplotlib.cbook

par exemple:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s

On dirait que vous avez accidentellement créé deux comptes. Vous pouvez contacter l'équipe pour les fusionner, ce qui vous permettra de retrouver des privilèges d'édition directe sur vos contributions.
Georgy
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.