วิธีแบ่งส่วนที่ทำซ้ำได้ในขนาดคงที่


87

เป็นไปได้ที่ซ้ำกัน:
คุณแบ่งรายการออกเป็นชิ้นขนาดเท่า ๆ กันใน Python ได้อย่างไร?

ฉันประหลาดใจที่ไม่พบฟังก์ชัน "แบตช์" ที่จะใช้เป็นอินพุตที่สามารถทำซ้ำได้

ตัวอย่างเช่น:

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

หรือ:

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

ตอนนี้ฉันเขียนสิ่งที่ฉันคิดว่าเป็นเครื่องกำเนิดไฟฟ้าที่ค่อนข้างง่าย:

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

ดังนั้นฉันพลาดอะไรไปและนี่อาจแสดงให้เห็นว่าฉันขาดความเข้าใจอย่างสมบูรณ์เกี่ยวกับเครื่องกำเนิด python ใครจะสนใจชี้ฉันไปในทิศทางที่ถูกต้อง?

[แก้ไข: ในที่สุดฉันก็รู้ว่าพฤติกรรมข้างต้นเกิดขึ้นเฉพาะเมื่อฉันเรียกใช้สิ่งนี้ภายใน ipython แทนที่จะเป็น python เอง]


คำถามดีเขียนดี แต่มีอยู่แล้วและจะแก้ปัญหาของคุณได้
Josh Smeaton

7
IMO นี่ไม่ได้ซ้ำกันจริงๆ คำถามอื่นจะเน้นไปที่รายการแทนที่จะเป็นตัวทำซ้ำและคำตอบส่วนใหญ่ต้องการ len () ซึ่งไม่เป็นที่ต้องการสำหรับผู้ทำซ้ำ แต่เอ๊ะคำตอบที่ได้รับการยอมรับในปัจจุบันยังต้องใช้ len () ดังนั้น ...
dequis

7
เห็นได้ชัดว่าไม่ซ้ำกัน คำถาม & คำตอบอื่น ๆใช้ได้กับรายการเท่านั้นและคำถามนี้เกี่ยวกับการพูดคุยทั่วไปสำหรับการทำซ้ำทั้งหมดซึ่งเป็นคำถามที่ฉันคิดไว้เมื่อมาที่นี่
Mark E.Haase

1
@JoshSmeaton @casperOne นี่ไม่ใช่รายการที่ซ้ำกันและคำตอบที่ยอมรับนั้นไม่ถูกต้อง คำถามที่ซ้ำกันที่เชื่อมโยงมีไว้สำหรับรายการและสามารถทำซ้ำได้ list มีวิธี len () แต่ทำซ้ำได้ไม่ได้ให้ len () method และคำตอบจะแตกต่างกันโดยไม่ต้องใช้ len () นี่คือคำตอบที่ถูกต้อง: batch = (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *[iter(iterable)] * n))
Trideep Rath

@TrideepRath ใช่ฉันโหวตให้เปิดอีกครั้ง
Josh Smeaton

คำตอบ:


126

อาจมีประสิทธิภาพมากกว่า (เร็วกว่า)

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

ตัวอย่างการใช้รายการ

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]

หลีกเลี่ยงการสร้างรายการใหม่


4
สำหรับบันทึกนี่เป็นวิธีแก้ปัญหาที่เร็วที่สุดที่ฉันพบ: ของฉัน = 4.5s, ของคุณ = 0.43s, Donkopotamus = 14.8s
mathieu

77
ในความเป็นจริงแบทช์ของคุณยอมรับรายการ (พร้อม len ()) ไม่สามารถทำซ้ำได้ (ไม่มี len ())
tdihp

31
เร็วกว่าเพราะไม่ใช่วิธีแก้ปัญหา สูตรปลาเก๋าโดย Raymond Hettinger ซึ่งปัจจุบันอยู่ด้านล่างนี้คือสิ่งที่คุณกำลังมองหาวิธีแก้ปัญหาทั่วไปที่ไม่ต้องการให้วัตถุป้อนข้อมูลมีวิธีการlen
Robert E Mealey

7
ทำไมคุณถึงใช้ min ()? ไม่มีmin()รหัสถูกต้อง!
Pavel Patrin

21
Iterablesไม่ได้len(), ลำดับมีlen()
คอส

64

FWIW สูตรอาหารในโมดูล itertoolsให้ตัวอย่างนี้:

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

มันทำงานดังนี้:

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

13
นี่ไม่ใช่สิ่งที่ฉันต้องการอย่างแน่นอนเนื่องจากมันเสริมองค์ประกอบสุดท้ายด้วยชุด None กล่าวคือไม่มีเป็นค่าที่ถูกต้องในข้อมูลที่ฉันใช้กับฟังก์ชันของฉันจริงๆดังนั้นสิ่งที่ฉันต้องการแทนคือสิ่งที่ไม่รองรายการสุดท้าย
mathieu

12
@mathieu แทนที่izip_longestด้วยizipซึ่งจะไม่ปิดทับรายการสุดท้าย แต่จะตัดรายการออกเมื่อองค์ประกอบบางส่วนเริ่มหมด
GoogieK

3
ควรเป็น zip_longest / zip ใน python 3
Peter Gerdes

5
@GoogieK for x, y in enumerate(grouper(3, xrange(10))): print(x,y)ไม่ได้เติมเต็มค่า แต่เพียงแค่ลดส่วนที่ไม่สมบูรณ์ลงไปทั้งหมด
kadrach

3
เป็นซับเดียวที่ดร็อปองค์ประกอบสุดท้ายหากไม่สมบูรณ์: list(zip(*[iter(iterable)] * n)). นี่จะต้องเป็นโค้ด python ที่ใกล้เคียงที่สุดที่ฉันเคยเห็น
Le Frite

31

ดังที่คนอื่น ๆ ได้กล่าวไว้ว่ารหัสที่คุณให้มานั้นเป็นสิ่งที่คุณต้องการ สำหรับวิธีการอื่นitertools.isliceคุณสามารถดูตัวอย่างของสูตรต่อไปนี้:

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 ไม่ใช่ ... รหัสนี้ใช้การเรียกnext()เพื่อทำให้StopIterationครั้งเดียวsourceiterหมดจึงยุติการทำซ้ำ หากไม่มีการเรียกnextมันจะยังคงส่งคืนตัวทำซ้ำที่ว่างเปล่าไปเรื่อย ๆ
donkopotamus

7
ฉันต้องแทนที่batchiter.next()ด้วยnext(batchiter)เพื่อให้โค้ดด้านบนทำงานใน Python 3
Martin Wiebusch

2
ชี้ให้เห็นความคิดเห็นจากบทความที่เชื่อมโยง: "คุณควรเพิ่มคำเตือนว่าต้องใช้แบตช์ทั้งหมดก่อนจึงจะดำเนินการต่อไปได้" map(list, batch(xrange(10), 3))การส่งออกของนี้ควรบริโภคกับสิ่งที่ต้องการ: การทำ: list(batch(xrange(10), 3)จะให้ผลลัพธ์ที่ไม่คาดคิด
Nathan Buesgens

2
ไม่ทำงานบน py3 .next()จะต้องเปลี่ยนเป็นnext(..)และlist(batch(range(0,10),3))พ่นRuntimeError: generator raised StopIteration
mathieu

1
@mathieu: ตัดการwhileวนซ้ำtry:/ except StopIteration: returnเพื่อแก้ไขปัญหาหลัง
ShadowRanger

13

ฉันแค่ให้คำตอบเดียว อย่างไรก็ตามตอนนี้ฉันรู้สึกว่าทางออกที่ดีที่สุดอาจไม่ใช่การเขียนฟังก์ชันใหม่ ๆ More-itertoolsมีเครื่องมือเพิ่มเติมมากมายและchunkedเป็นหนึ่งในนั้น


นี่เป็นคำตอบที่เหมาะสมที่สุด (แม้ว่าจะต้องติดตั้งแพ็คเกจเพิ่มอีกหนึ่งแพ็คเกจ) และยังichunkedให้ผลลัพธ์ซ้ำ
viddik13

10

แปลกดูเหมือนว่าจะทำงานได้ดีสำหรับฉันใน 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]

คำตอบที่ดีเพราะไม่ต้องนำเข้าอะไรและอ่านง่าย
ถึง

8

นี่เป็นข้อมูลโค้ดสั้น ๆ ที่ฉันรู้ว่าไม่ได้ใช้lenและทำงานภายใต้ทั้ง Python 2 และ 3 (ไม่ใช่การสร้างของฉัน):

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

วิธีแก้ปัญหาสำหรับ Python 3.8 หากคุณกำลังทำงานกับ iterables ที่ไม่ได้กำหนดlenฟังก์ชันและหมด:

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

ตัวอย่างการใช้งาน:

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]

แน่นอนว่าสามารถใช้งานได้โดยไม่ต้องใช้ตัวดำเนินการวอลรัสเช่นกัน


3
ในเวอร์ชันปัจจุบันbatcherยอมรับตัววนซ้ำไม่ใช่ตัวทำซ้ำ มันจะส่งผลให้เกิดการวนซ้ำแบบไม่มีที่สิ้นสุดด้วยรายการ ควรมีเส้นiterator = iter(iterable)ก่อนเริ่มwhileลูป
Daniel Perez

2

นี่คือสิ่งที่ฉันใช้ในโครงการของฉัน จัดการรายการซ้ำหรือรายการได้อย่างมีประสิทธิภาพที่สุด

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

คำตอบที่ดีที่สุดใช้ได้กับทุกโครงสร้างข้อมูล
ClémentPrévost

1

วิธีนี้จะใช้งานได้สำหรับการทำซ้ำ

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

มันจะทำงานดังนี้:

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

PS: มันจะไม่ทำงานถ้าทำซ้ำได้ไม่มีค่า


1

นี่คือวิธีการใช้reduceฟังก์ชัน

หนึ่งในสายการบิน:

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

หรือรุ่นที่อ่านได้เพิ่มเติม:

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

ทดสอบ:

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

เวอร์ชันที่ใช้งานได้โดยไม่มีคุณสมบัติใหม่ใน python 3.8 ซึ่งดัดแปลงมาจากคำตอบของ @Atra Azami

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)

เอาท์พุต:

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

0

คุณสามารถจัดกลุ่มรายการที่ทำซ้ำได้ตามดัชนีชุดงาน

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

มักเป็นกรณีที่คุณต้องการรวบรวมการวนซ้ำภายในดังนั้นนี่คือเวอร์ชันขั้นสูง

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

ตัวอย่าง:

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

ฟังก์ชันที่เกี่ยวข้องที่คุณอาจต้องการ:

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

การใช้งาน:

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

มันได้รับชุดฉันจากลำดับและสามารถทำงานกับโครงสร้างข้อมูลอื่น ๆ ได้เช่นกันเช่นแพนด้าดาต้าเฟรม ( df.iloc[batch(100,0)]) หรืออาร์เรย์ numpy ( array[batch(100,0)])


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

ฉันใช้

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

ใช้ n องค์ประกอบ (มากที่สุด) ต่อไปจนกว่าจะหมด

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.