cách chia một phần có thể lặp lại thành các phần có kích thước không đổi


85

Có thể trùng lặp:
Làm cách nào để bạn chia danh sách thành các phần có kích thước đồng đều trong Python?

Tôi rất ngạc nhiên vì tôi không thể tìm thấy một hàm "batch" sẽ lấy làm đầu vào là một tệp có thể lặp lại và trả về một tệp lặp có thể lặp lại.

Ví dụ:

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

hoặc là:

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

Bây giờ, tôi đã viết những gì tôi nghĩ là một máy phát điện khá đơn giản:

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

Nhưng những điều trên không mang lại cho tôi những gì tôi mong đợi:

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]

Vì vậy, tôi đã bỏ lỡ điều gì đó và điều này có thể cho thấy sự thiếu hiểu biết của tôi về trình tạo python. Có ai muốn chỉ cho tôi đi đúng hướng không?

[Chỉnh sửa: Cuối cùng tôi nhận ra rằng hành vi trên chỉ xảy ra khi tôi chạy điều này trong ipython chứ không phải chính python]


Câu hỏi hay, được viết tốt, nhưng nó đã tồn tại và sẽ giải quyết được vấn đề của bạn.
Josh Smeaton

7
IMO đây thực sự không phải là một bản sao. Câu hỏi khác tập trung vào danh sách thay vì trình vòng lặp và hầu hết các câu trả lời đó yêu cầu len (), điều này không mong muốn đối với trình vòng lặp. Nhưng eh, câu trả lời hiện được chấp nhận ở đây cũng yêu cầu len (), vì vậy ...
dequis

7
Đây rõ ràng không phải là một bản sao. Câu hỏi & Đáp khác chỉ hoạt động cho các danh sách và câu hỏi này là về việc tổng quát hóa cho tất cả các mục lặp lại, đó chính xác là câu hỏi tôi đã nghĩ khi đến đây.
Mark E. Haase

1
@JoshSmeaton @casperMột cái này không phải là bản sao và câu trả lời được chấp nhận là không đúng. Câu hỏi trùng lặp được liên kết dành cho danh sách và câu hỏi này có thể lặp lại. danh sách cung cấp len () phương pháp nhưng iterable không cung cấp một phương pháp len () và câu trả lời sẽ khác nhau mà không sử dụng len () Đây là câu trả lời đúng: batch = (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *[iter(iterable)] * n))
Trideep Rath

@TrideepRath vâng, tôi đã bỏ phiếu để mở lại.
Josh Smeaton

Câu trả lời:


117

Điều này có lẽ hiệu quả hơn (nhanh hơn)

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

Ví dụ sử dụng danh sách

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]

Nó tránh xây dựng danh sách mới.


4
Đối với hồ sơ, đây là giải pháp nhanh nhất tôi tìm thấy: tôi = 4.5s, bạn = 0.43s, Donkopotamus = 14.8s
Mathieu

74
batch trên thực tế chấp nhận một danh sách (với len ()), không iterable (không len ())
tdihp

28
Điều này nhanh hơn vì nó không phải là giải pháp cho vấn đề. Công thức cá mú của Raymond Hettinger - hiện ở dưới đây - là những gì bạn đang tìm kiếm một giải pháp chung không yêu cầu đối tượng đầu vào phải có phương thức len .
Robert E Mealey

7
Tại sao bạn sử dụng min ()? Không có min()mã là hoàn toàn chính xác!
Pavel Patrin

20
Các phần lặp lại không có len(), các chuỗilen()
Kos

60

FWIW, công thức nấu ăn trong mô-đun itertools cung cấp ví dụ sau:

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

Nó hoạt động như thế này:

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

13
Đây không phải là chính xác những gì tôi cần vì nó đệm phần tử cuối cùng bằng một tập hợp Không có. tức là, Không có là giá trị hợp lệ trong dữ liệu mà tôi thực sự sử dụng với hàm của mình nên thay vào đó, thứ tôi cần là thứ không chèn vào mục cuối cùng.
mathieu

12
@mathieu Thay thế izip_longestbằng izip, sẽ không chèn các mục cuối cùng mà thay vào đó sẽ cắt các mục nhập khi một số phần tử bắt đầu hết.
GoogieK

3
Nên là zip_longest / zip trong python 3
Peter Gerdes

5
@GoogieK for x, y in enumerate(grouper(3, xrange(10))): print(x,y)thực sự không lấp đầy các giá trị, nó chỉ loại bỏ hoàn toàn phân đoạn chưa hoàn chỉnh.
kadrach

3
Như một lót mà giọt yếu tố cuối cùng nếu không đầy đủ: list(zip(*[iter(iterable)] * n)). Đây phải là đoạn mã python gọn gàng nhất mà tôi từng thấy.
Le Frite

31

Như những người khác đã lưu ý, mã bạn đã cung cấp thực hiện chính xác những gì bạn muốn. Đối với một cách tiếp cận khác bằng cách sử dụng, itertools.islicebạn có thể xem một ví dụ về công thức sau:

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 Không ... mã này sử dụng lệnh gọi next()để gây ra một StopIterationlần sourceiterbị cạn kiệt, do đó chấm dứt trình lặp. Nếu không có lệnh gọi nextnó sẽ tiếp tục trả về các trình lặp trống vô thời hạn.
donkopotamus

7
Tôi đã phải thay thế batchiter.next()bằng next(batchiter)để làm cho công việc mã trên bằng Python 3.
Martin Wiebusch

2
chỉ ra một nhận xét từ bài viết được liên kết: "Bạn nên thêm cảnh báo rằng một lô phải được tiêu thụ hết trước khi bạn có thể chuyển sang lô tiếp theo." Kết quả của việc này nên được tiêu thụ với một cái gì đó như: map(list, batch(xrange(10), 3)). Thực hiện: list(batch(xrange(10), 3)sẽ cho kết quả bất ngờ.
Nathan Buesgens 22/09/17

2
Không hoạt động trên py3. .next()phải được thay đổi để next(..), và list(batch(range(0,10),3))némRuntimeError: generator raised StopIteration
Mathieu

1
@mathieu: Kết thúc whilevòng lặp trong try:/ except StopIteration: returnđể khắc phục sự cố sau.
ShadowRanger

13

Tôi chỉ đưa ra một câu trả lời. Tuy nhiên, bây giờ tôi cảm thấy giải pháp tốt nhất có thể là không viết bất kỳ chức năng mới nào. More-itertools bao gồm nhiều công cụ bổ sung và chunkednằm trong số đó.


Đây thực sự là câu trả lời phù hợp nhất (mặc dù nó yêu cầu cài đặt thêm một gói) và cũng có ichunkedthể tạo ra các tệp lặp.
viddik13

10

Kỳ lạ, dường như hoạt động tốt đối với tôi trong 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]

Câu trả lời tuyệt vời vì nó không cần nhập bất cứ thứ gì và rất trực quan để đọc.
ojunk

8

Đây là một đoạn mã rất ngắn mà tôi biết không sử dụng lenvà hoạt động trong cả Python 2 và 3 (không phải do tôi tạo):

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

4

Giải pháp cho Python 3.8 nếu bạn đang làm việc với các tệp lặp không xác định một lenhàm và bị kiệt sức:

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

Ví dụ sử dụng:

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]

Tất nhiên có thể được thực hiện mà không có nhà điều hành hải mã.


1
Trong phiên bản hiện tại, batcherchấp nhận một trình lặp, không phải một trình lặp. Ví dụ, nó sẽ dẫn đến một vòng lặp vô hạn với một danh sách. Có lẽ phải có một dòng iterator = iter(iterable)trước khi bắt đầu whilevòng lặp.
Daniel Perez

2

Đây là những gì tôi sử dụng trong dự án của mình. Nó xử lý các tệp lặp hoặc danh sách hiệu quả nhất có thể.

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

Câu trả lời hay nhất cho đến nay, hoạt động với mọi cấu trúc dữ liệu
Clément Prévost

1

Điều này sẽ hoạt động cho bất kỳ có thể lặp lại.

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

Nó sẽ hoạt động như thế này:

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

Tái bút: Nó sẽ không hoạt động nếu có thể lặp lại không có giá trị nào.


1

Đây là một cách tiếp cận bằng cách sử dụng reducehàm.

Lót:

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

Hoặc phiên bản dễ đọc hơn:

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

Kiểm tra:

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

0

Bạn chỉ có thể nhóm các mục có thể lặp lại theo chỉ mục hàng loạt của chúng.

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

Nó thường xảy ra khi bạn muốn thu thập các vòng lặp bên trong, vì vậy đây là phiên bản nâng cao hơn.

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

Ví dụ:

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

Chức năng liên quan bạn có thể cần:

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

Sử dụng:

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

Nó lấy lô thứ i từ chuỗi và nó cũng có thể hoạt động với các cấu trúc dữ liệu khác, như pandas dataframes ( df.iloc[batch(100,0)]) hoặc numpy array ( 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

tôi sử dụng

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)]
  
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.