bir yinelenebilirliği sabit boyutlu parçalara bölme


87

Olası Yineleme:
Python'da bir listeyi eşit büyüklükte parçalara nasıl bölersiniz?

Girdi olarak yinelenebilen ve yinelenebilir yinelenebilir bir "toplu" işlevi bulamadığıma şaşırdım.

Örneğin:

for i in batch(range(0,10), 1): print i
[0]
[1]
...
[9]

veya:

for i in batch(range(0,10), 3): print i
[0,1,2]
[3,4,5]
[6,7,8]
[9]

Şimdi, oldukça basit olduğunu düşündüğüm şeyi yazdım:

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

Ancak yukarıdakiler bana beklediğim şeyi vermiyor:

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]

Yani, bir şeyi kaçırdım ve bu muhtemelen python jeneratörlerini tam olarak anlamadığımı gösteriyor. Beni doğru yöne yönlendirmek isteyen var mı?

[Düzenleme: Sonunda yukarıdaki davranışın sadece bunu python yerine ipython içinde çalıştırdığımda gerçekleştiğini fark ettim]


Güzel soru, iyi yazılmış, ama zaten var ve sorununuzu çözecek.
Josh Smeaton

7
IMO bu gerçekten bir kopya değil. Diğer soru yineleyiciler yerine listelere odaklanır ve bu yanıtların çoğu yineleyiciler için istenmeyen len () gerektirir. Ama eh, şu anda kabul edilen cevap burada len () de gerektiriyor, bu yüzden ...
dequis

7
Bu açıkça bir kopya değil. Diğer Soru-Cevap yalnızca listeler için işe yarar ve bu soru tüm yinelenenlere genelleme yapmakla ilgilidir, ki buraya geldiğimde aklıma tam olarak gelen soru buydu.
Mark E. Haase

1
@JoshSmeaton @casperOne bu bir kopya değil ve kabul edilen cevap doğru değil. Bağlantılı yinelenen soru liste içindir ve bu yinelenebilir. liste len () yöntemi sağlar, ancak yinelenebilir bir len () yöntemi sağlamaz ve yanıt, len () kullanılmadan farklı olacaktır Bu doğru yanıt: batch = (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *[iter(iterable)] * n))
Trideep Rath

@TrideepRath evet, yeniden açılmasına oy verdim.
Josh Smeaton

Yanıtlar:


126

Bu muhtemelen daha verimli (daha hızlı)

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

Liste kullanarak örnek

data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # list of data 

for x in batch(data, 3):
    print(x)

# Output

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

Yeni listeler oluşturmaktan kaçınır.


4
Kayıt için, bulduğum en hızlı çözüm bu: benimki = 4,5sn, seninki = 0,43sn, Donkopotamus = 14,8sn
mathieu

77
grubunuz aslında bir listeyi kabul ediyor (len () ile), yinelenemez (len () olmadan)
tdihp

31
Bu daha hızlıdır çünkü soruna bir çözüm değildir. Raymond Hettinger'ın orfoz tarifi - şu anda bunun altında - aradığınız şey, giriş nesnesinin bir len yöntemine sahip olmasını gerektirmeyen genel bir çözümdür .
Robert E Mealey

7
Neden min () kullanıyorsunuz? min()Kod olmadan tamamen doğru!
Pavel Patrin

21
Yinelemelerde yok len(), dizilerde varlen()
Kos

64

FWIW, itertools modülündeki tarifler şu örneği sağlar:

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

Şu şekilde çalışır:

>>> list(grouper(3, range(10)))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]

13
Bu, son öğeyi bir Yok kümesiyle doldurduğu için tam olarak ihtiyacım olan şey değil. Yani, Hiçbiri işlevimle kullandığım verilerde geçerli bir değerdir, bu yüzden bunun yerine ihtiyacım olan şey son girişi doldurmayan bir şeydir.
mathieu

12
@mathieu değiştirin izip_longestile iziphangi ped son girişleri olmaz, ancak unsurlardan bazılarının göstermeye başladıktan zaman yerine girişler kesti.
GoogieK

3
Python 3'te zip_longest / zip olmalıdır
Peter Gerdes

5
@GoogieK for x, y in enumerate(grouper(3, xrange(10))): print(x,y)gerçekten de değerleri doldurmaz, sadece tamamlanmamış segmenti tamamen bırakır.
kadrach

3
: Eğer eksik son öğe düşer tek astar olarak list(zip(*[iter(iterable)] * n)). Bu şimdiye kadar gördüğüm en temiz python kodu parçası olmalı.
Le Frite

31

Başkalarının da belirttiği gibi, verdiğiniz kod tam olarak istediğinizi yapıyor. Kullanarak başka bir yaklaşım için aşağıdaki tarifin itertools.islicebir örneğini görebilirsiniz :

from itertools import islice, chain

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

1
@abhilash Hayır ... Bu kod çağrısı kullanan next()bir neden StopIterationkez sourceiterdolayısıyla yineleyici sonlandırma, bitmiştir. Çağrı olmasaydı, nextsonsuza kadar boş yineleyiciler döndürmeye devam ederdi.
donkopotamus

7
Ben değiştirmek zorunda batchiter.next()olan next(batchiter)Python 3'te Yukarıdaki kod çalışması için
Martin Wiebusch

2
bağlantılı makaleden bir yoruma işaret ederek: "Bir sonrakine geçmeden önce bir partinin tamamen tüketilmesi gerektiğine dair bir uyarı eklemelisiniz." Bu çıktısı gibi bir şeyle tüketilmelidir: map(list, batch(xrange(10), 3)). Yapmak: list(batch(xrange(10), 3)beklenmedik sonuçlar doğurur.
Nathan Buesgens

2
Py3 üzerinde çalışmaz. .next()olarak değiştirilmeli next(..)ve list(batch(range(0,10),3))atarRuntimeError: generator raised StopIteration
mathieu

1
@mathieu: Wrap whiledöngü try:/ ' except StopIteration: returnikinci sorunu gidermek için.
ShadowRanger

13

Sadece bir cevap verdim. Ancak şimdi en iyi çözümün yeni işlevler yazmamak olabileceğini düşünüyorum. More-itertools birçok ek araç içerir ve chunkedbunların arasındadır.


Bu gerçekten en uygun cevaptır (bir paketin daha yüklenmesini gerektirmesine rağmen) ve bu da ichunkedyinelenebilir sonuçlar verir.
viddik13

10

Garip, Python 2.x'te benim için iyi çalışıyor gibi görünüyor

>>> 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]

Harika yanıt çünkü herhangi bir şeyi içe aktarması gerekmiyor ve okuması sezgisel.
ojunk

8

Bu, lenPython 2 ve 3 altında kullanılmayan ve çalıştığını bildiğim çok kısa bir kod parçacığıdır (benim oluşturmam değil):

def chunks(iterable, size):
    from itertools import chain, islice
    iterator = iter(iterable)
    for first in iterator:
        yield list(chain([first], islice(iterator, size - 1)))

7

Bir lenişlevi tanımlamayan yinelenenlerle çalışıyorsanız ve yoruluyorsanız Python 3.8 için çözüm :

def batcher(iterable, batch_size):
    while batch := list(islice(iterable, batch_size)):
        yield batch

Örnek kullanım:

def my_gen():
    yield from range(10)
 
for batch in batcher(my_gen(), 3):
    print(batch)

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

Elbette mors operatörü olmadan da uygulanabilir.


3
Mevcut sürümde, batcherbir yineleyici değil, bir yineleyici kabul eder. Örneğin bir liste içeren sonsuz bir döngü ile sonuçlanır. Döngüye iterator = iter(iterable)başlamadan önce muhtemelen bir satır olmalıdır while.
Daniel Perez

2

Projemde kullandığım şey bu. Yinelemeleri veya listeleri olabildiğince verimli bir şekilde işler.

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()]

2
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]]

Şimdiye kadarki en iyi yanıt, her veri yapısıyla çalışır
Clément Prévost

1

Bu, herhangi bir yinelenebilir için işe yarar.

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))

Şu şekilde çalışır:

>>>list(batch_iterable(range(0,5)), 2)
[(0, 1), (2, 3), (4,)]

Not: Yinelemenin Yok değerleri varsa çalışmaz.


1

İşte kullanan bir yaklaşım reduce işlevi .

Oneliner:

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, [[]])

Veya daha okunaklı versiyon:

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, [[]])

Ölçek:

>>> 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]]

1

@Atra Azami'nin cevabından uyarlanmış, python 3.8'de yeni özellikler içermeyen uygulanabilir bir sürüm.

import itertools    

def batch_generator(iterable, batch_size=1):
    iterable = iter(iterable)

    while True:
        batch = list(itertools.islice(iterable, batch_size))
        if len(batch) > 0:
            yield batch
        else:
            break

for x in batch_generator(range(0, 10), 3):
    print(x)

Çıktı:

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

0

Yinelenebilir öğeleri toplu indekslerine göre gruplayabilirsiniz.

def batch(items: Iterable, batch_size: int) -> Iterable[Iterable]:
    # enumerate items and group them by batch index
    enumerated_item_groups = itertools.groupby(enumerate(items), lambda t: t[0] // batch_size)
    # extract items from enumeration tuples
    item_batches = ((t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    return item_batches

İçsel yinelemeleri toplamak istediğinizde genellikle durum böyledir, bu yüzden burada daha gelişmiş sürüm var.

def batch_advanced(items: Iterable, batch_size: int, batches_mapper: Callable[[Iterable], Any] = None) -> Iterable[Iterable]:
    enumerated_item_groups = itertools.groupby(enumerate(items), lambda t: t[0] // batch_size)
    if batches_mapper:
        item_batches = (batches_mapper(t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    else:
        item_batches = ((t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    return item_batches

Örnekler:

print(list(batch_advanced([1, 9, 3, 5, 2, 4, 2], 4, tuple)))
# [(1, 9, 3, 5), (2, 4, 2)]
print(list(batch_advanced([1, 9, 3, 5, 2, 4, 2], 4, list)))
# [[1, 9, 3, 5], [2, 4, 2]]

0

İhtiyaç duyabileceğiniz ilgili işlevler:

def batch(size, i):
    """ Get the i'th batch of the given size """
    return slice(size* i, size* i + size)

Kullanım:

>>> [1,2,3,4,5,6,7,8,9,10][batch(3, 1)]
>>> [4, 5, 6]

Sıradan i'inci grubu alır ve pandas dataframes ( df.iloc[batch(100,0)]) veya numpy array ( array[batch(100,0)]) gibi diğer veri yapılarıyla da çalışabilir .


0
from itertools import *

class SENTINEL: pass

def batch(iterable, n):
    return (tuple(filterfalse(lambda x: x is SENTINEL, group)) for group in zip_longest(fillvalue=SENTINEL, *[iter(iterable)] * n))

print(list(range(10), 3)))
# outputs: [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]
print(list(batch([None]*10, 3)))
# outputs: [(None, None, None), (None, None, None), (None, None, None), (None,)]

0

kullanırım

def batchify(arr, batch_size):
  num_batches = math.ceil(len(arr) / batch_size)
  return [arr[i*batch_size:(i+1)*batch_size] for i in range(num_batches)]
  

0

Bitene kadar (en fazla) n öğe almaya devam edin.

def chop(n, iterable):
    iterator = iter(iterable)
    while chunk := list(take(n, iterator)):
        yield chunk


def take(n, iterable):
    iterator = iter(iterable)
    for i in range(n):
        try:
            yield next(iterator)
        except StopIteration:
            return
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.