Dupliquer possible:
Comment divisez-vous une liste en morceaux de taille égale en Python?
Je suis surpris de ne pas pouvoir trouver une fonction "batch" qui prendrait comme entrée un itérable et renverrait un itérable de iterables.
Par exemple:
for i in batch(range(0,10), 1): print i
[0]
[1]
...
[9]
ou:
for i in batch(range(0,10), 3): print i
[0,1,2]
[3,4,5]
[6,7,8]
[9]
Maintenant, j'ai écrit ce que je pensais être un générateur assez simple:
def batch(iterable, n = 1):
current_batch = []
for item in iterable:
current_batch.append(item)
if len(current_batch) == n:
yield current_batch
current_batch = []
if current_batch:
yield current_batch
Mais ce qui précède ne me donne pas ce à quoi je m'attendais:
for x in batch(range(0,10),3): print x
[0]
[0, 1]
[0, 1, 2]
[3]
[3, 4]
[3, 4, 5]
[6]
[6, 7]
[6, 7, 8]
[9]
J'ai donc manqué quelque chose et cela montre probablement mon manque total de compréhension des générateurs de python. Quelqu'un voudrait me diriger dans la bonne direction?
[Edit: j'ai finalement réalisé que le comportement ci-dessus ne se produit que lorsque je lance ceci dans ipython plutôt que python lui-même]
C'est probablement plus efficace (plus rapide)
def batch(iterable, n=1):
l = len(iterable)
for ndx in range(0, l, n):
yield iterable[ndx:min(ndx + n, l)]
for x in batch(range(0, 10), 3):
print x
Cela évite de construire de nouvelles listes.
FWIW, les recettes du module itertools fournit cet exemple:
def grouper(n, iterable, fillvalue=None):
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)
Cela fonctionne comme ceci:
>>> list(grouper(3, range(10)))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]
Comme d'autres l'ont noté, le code que vous avez donné fait exactement ce que vous voulez. Pour une autre approche utilisant itertools.islice
, vous pourriez voir un exemple de la recette suivante:
from itertools import islice, chain
def batch(iterable, size):
sourceiter = iter(iterable)
while True:
batchiter = islice(sourceiter, size)
yield chain([batchiter.next()], batchiter)
Bizarre, semble bien fonctionner pour moi dans Python 2.x
>>> def batch(iterable, n = 1):
... current_batch = []
... for item in iterable:
... current_batch.append(item)
... if len(current_batch) == n:
... yield current_batch
... current_batch = []
... if current_batch:
... yield current_batch
...
>>> for x in batch(range(0, 10), 3):
... print x
...
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]
Je viens de donner une réponse. Cependant, je pense maintenant que la meilleure solution pourrait être de ne pas écrire de nouvelles fonctions. More-itertools inclut de nombreux outils supplémentaires, et chunked
en fait partie.
C'est un extrait de code très court que je connais (pas ma création) qui n'utilise pas len
et fonctionne à la fois avec Python 2 et 3 (pas ma création):
def chunks(iterable, size):
from itertools import chain, islice
iterator = iter(iterable)
for first in iterator:
yield list(chain([first], islice(iterator, size - 1)))
def batch(iterable, n):
iterable=iter(iterable)
while True:
chunk=[]
for i in range(n):
try:
chunk.append(next(iterable))
except StopIteration:
yield chunk
return
yield chunk
list(batch(range(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
Cela fonctionnerait pour tout itérable.
from itertools import Zip_longest, filterfalse
def batch_iterable(iterable, batch_size=2):
args = [iter(iterable)] * batch_size
return (Tuple(filterfalse(lambda x: x is None, group)) for group in Zip_longest(fillvalue=None, *args))
Cela fonctionnerait comme ceci:
>>>list(batch_iterable(range(0,5)), 2)
[(0, 1), (2, 3), (4,)]
PS: Cela ne fonctionnerait pas si iterable avait des valeurs None.
C'est ce que j'utilise dans mon projet. Il gère les iterables ou les listes aussi efficacement que possible.
def chunker(iterable, size):
if not hasattr(iterable, "__len__"):
# generators don't have len, so fall back to slower
# method that works with generators
for chunk in chunker_gen(iterable, size):
yield chunk
return
it = iter(iterable)
for i in range(0, len(iterable), size):
yield [k for k in islice(it, size)]
def chunker_gen(generator, size):
iterator = iter(generator)
for first in iterator:
def chunk():
yield first
for more in islice(iterator, size - 1):
yield more
yield [k for k in chunk()]
Voici une approche utilisant la fonction reduce
.
Bon mot:
from functools import reduce
reduce(lambda cumulator,item: cumulator[-1].append(item) or cumulator if len(cumulator[-1]) < batch_size else cumulator + [[item]], input_array, [[]])
Ou version plus lisible:
from functools import reduce
def batch(input_list, batch_size):
def reducer(cumulator, item):
if len(cumulator[-1]) < batch_size:
cumulator[-1].append(item)
return cumulator
else:
cumulator.append([item])
return cumulator
return reduce(reducer, input_list, [[]])
Tester:
>>> batch([1,2,3,4,5,6,7], 3)
[[1, 2, 3], [4, 5, 6], [7]]
>>> batch(a, 8)
[[1, 2, 3, 4, 5, 6, 7]]
>>> batch([1,2,3,None,4], 3)
[[1, 2, 3], [None, 4]]