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


J'ai une liste de longueur arbitraire, et j'ai besoin de la diviser en morceaux de taille égale et d'opérer dessus. Il y a quelques façons évidentes de faire cela, comme garder un compteur et deux listes, et quand 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 cher.

Je me demandais si quelqu'un avait une bonne solution à cette question pour des listes de longueur quelconque, p. en utilisant des générateurs.

Je cherchais quelque chose d'utile dans itertools mais je n'ai rien trouvé d'utile. Peut-être l'aurais-je manqué, cependant.

Question connexe: Quelle est la manière la plus "pythonique" de parcourir une liste en morceaux?


1578
2017-11-23 12:15


origine


Réponses:


Voici un générateur qui génère les morceaux que vous voulez:

def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[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 xrange() au lieu de range():

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

En outre, vous pouvez simplement utiliser la compréhension de liste au lieu d'écrire une fonction. Python 3:

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

Version Python 2:

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

2113
2017-11-23 12:33



Si vous voulez quelque chose de super simple:

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

481
2017-11-17 20:17



Directement à partir de la (ancienne) 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 J.F.Sebastian:

#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 - travaillera - fonctionnera - fonctionnera à nouveau.

Ces solutions fonctionnent parce que [iter(iterable)]*n (ou l'équivalent dans la version antérieure) crée un itérateur, répété n fois dans la liste. izip_longest puis effectue efficacement un round-robin de "chaque" itérateur; parce que c'est le même itérateur, il est avancé par chacun de ces appels, ce qui fait que chaque fichier zip-round génère un tuple de n articles.


251
2017-11-23 15:48



Je sais que c'est un peu vieux mais je ne sais pas pourquoi personne n'a mentionné numpy.array_split:

lst = range(50)
In [26]: np.array_split(lst,5)
Out[26]: 
[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])]

94
2018-06-05 08:54



Voici un générateur qui fonctionne sur des itérations 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]]

79
2017-11-23 12:41



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

from itertools import islice

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

Démonstration

>>> 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 paresseusement la production. 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 un rembourrage, une simple variation sur 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émonstration

>>> 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 le izip_longestsolutions basées sur le toujours tampons. Pour autant que je sache, il n'y a pas de recette d'une ou deux lignes itertools pour une fonction facultativement tampons. En combinant les deux approches ci-dessus, celle-ci est assez proche:

_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émonstration

>>> 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 crois que c'est le plus court chunker proposé qui offre un rembourrage optionnel.


65
2018-02-26 15:02



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

48
2018-06-26 19:10



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:

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

39
2017-07-12 07:58



J'ai vu la réponse Python-ish la plus impressionnante dans un dupliquer 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. 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 remplacer zip_longest avec zip, sinon le triplet (13, 14, None) serait perdu. Python 3 est utilisé ci-dessus. Pour Python 2, utilisez izip_longest.


28
2018-03-12 12:36



Critique d'autres réponses ici:

Aucune de ces réponses ne sont des morceaux de taille égale, ils laissent tous un petit morceau à la fin, donc ils ne sont pas complètement équilibrés. Si vous utilisiez ces fonctions pour répartir le travail, vous avez intégré la perspective d'une fin probable bien avant les autres, de sorte qu'il resterait assis à ne rien faire pendant que les autres continueraient à travailler dur.

Par exemple, la meilleure 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 chunk(xrange(7), 3) les deux reviennent: [(0, 1, 2), (3, 4, 5), (6, None, None)]. le Nonesont juste rembourrage, et plutôt inélégant à mon avis. Ils ne répartissent pas uniformément les itérations.

Pourquoi ne pouvons-nous pas les diviser mieux?

Ma solution (s)

Voici une solution équilibrée, adaptée d'une fonction que j'ai utilisée en production (Note en Python 3 pour remplacer xrange avec 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 la même chose 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 retournent 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)]

Sortie

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 blocs de même longueur que les deux autres, mais les éléments sont tous dans l'ordre, et ils sont aussi divisés que l'on peut diviser une liste d'éléments discrets.


26
2018-02-13 23:07