Cách thức pythonic nhất của người Viking để lặp đi lặp lại qua một danh sách theo từng khối là gì?


488

Tôi có một tập lệnh Python lấy đầu vào là danh sách các số nguyên mà tôi cần để làm việc với bốn số nguyên cùng một lúc. Thật không may, tôi không có quyền kiểm soát đầu vào, hoặc tôi đã chuyển nó dưới dạng một danh sách các bộ dữ liệu bốn yếu tố. Hiện tại, tôi đang lặp lại theo cách này:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

Tuy nhiên, nó trông rất giống "C-think", điều này khiến tôi nghi ngờ có cách xử lý tình huống khó xử hơn trong tình huống này. Danh sách bị loại bỏ sau khi lặp, vì vậy nó không cần phải được lưu giữ. Có lẽ một cái gì đó như thế này sẽ tốt hơn?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

Tuy nhiên, vẫn không hoàn toàn "cảm thấy" đúng. : - /

Câu hỏi liên quan: Làm thế nào để bạn chia một danh sách thành các phần có kích thước bằng nhau trong Python?


3
Mã của bạn không hoạt động nếu kích thước danh sách không phải là bội số của bốn.
Pedro Henriques

5
Tôi mở rộng () trong danh sách để độ dài của nó là bội số của bốn trước khi nó đi xa đến mức này.
Ben Trống

4
@ - Các câu hỏi rất giống nhau, nhưng không hoàn toàn trùng lặp. Đó là "chia thành bất kỳ số lượng kích thước N" so với "chia thành N khối có kích thước bất kỳ". :-)
Ben Trống


Câu trả lời:


340

Được sửa đổi từ phần công thức nấu ăn của tài liệu itertools của Python :

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Ví dụ
Trong mã giả để giữ ví dụ ngắn gọn.

grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

Ghi chú: trên Python 2 sử dụng izip_longestthay vì zip_longest.


67
Cuối cùng cũng có cơ hội để chơi xung quanh với điều này trong một phiên python. Đối với những người bối rối như tôi, điều này đang cung cấp cùng một trình lặp cho izip_longest nhiều lần, khiến nó tiêu thụ các giá trị liên tiếp của cùng một chuỗi thay vì các giá trị sọc từ các chuỗi riêng biệt. Tôi thích nó!
Ben Trống

6
Cách tốt nhất để lọc ra fillvalue là gì? ([mục cho mục trong mục nếu mục không phải là giá trị] cho mục trong cá mú (có thể lặp lại))?
gotgenes

14
Tôi nghi ngờ rằng hiệu suất của công thức cá mú này cho các khối có kích thước 256k sẽ rất kém, bởi vì izip_longestsẽ được cung cấp các đối số 256k.
techtonik giải phẫu

13
Ở một số nơi, các nhà bình luận nói rằng "khi cuối cùng tôi đã tìm ra cách nó hoạt động ...." Có thể cần một chút giải thích. Đặc biệt là danh sách các khía cạnh lặp.
LondonRob

6
Có cách nào để sử dụng cái này nhưng không Nonelấp đầy phần cuối cùng không?
CMCDragon

420
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

Đơn giản. Dễ dàng. Nhanh. Hoạt động với bất kỳ trình tự:

text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']

16
Phiên bản của @Carlos Crasborn hoạt động cho mọi lần lặp (không chỉ các chuỗi như mã trên); nó ngắn gọn và có thể nhanh hoặc thậm chí nhanh hơn. Mặc dù nó có thể hơi mơ hồ (không rõ ràng) cho những người không quen thuộc với itertoolsmô-đun.
JFS

1
Đã đồng ý. Đây là cách chung nhất và pythonic. Rõ ràng và súc tích. (và hoạt động trên công cụ ứng dụng)
Matt Williamson

3
Lưu ý rằng chunkertrả về a generator. Thay thế trả về: return [...]để có được một danh sách.
Dror

11
Thay vì viết một hàm xây dựng và sau đó trả về một trình tạo, bạn cũng có thể viết một trình tạo trực tiếp, sử dụng yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Tôi không chắc chắn nếu bên trong điều này sẽ được xử lý khác nhau trong bất kỳ khía cạnh liên quan nào, nhưng nó thậm chí có thể rõ ràng hơn một chút.
Alfe

3
Lưu ý rằng điều này chỉ hoạt động đối với các chuỗi hỗ trợ các mục truy cập theo chỉ mục và sẽ không hoạt động đối với các trình vòng lặp chung, vì chúng có thể không hỗ trợ __getitem__phương thức.
apollov

135

tôi là fan của

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size

Nó hoạt động như thế nào nếu len (ints) không phải là bội số của chunkSize?
PlsWork 17/2/19

3
@AnnaVopureta chunksẽ có 1, 2 hoặc 3 phần tử cho lô phần tử cuối cùng. Xem câu hỏi này về lý do tại sao các chỉ số lát có thể nằm ngoài giới hạn .
Boris

22
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

Cách khác:

import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4

2
+1 cho việc sử dụng máy phát điện, các đường nối giống như "pythonic" nhất trong số tất cả các giải pháp được đề xuất
Sergey Golovchenko

7
Nó khá dài và vụng về cho một thứ gì đó quá dễ dàng, điều đó không hề hay ho gì cả. Tôi thích phiên bản của S. Lott
zenazn

4
@zenazn: điều này sẽ hoạt động trên các trường hợp máy phát điện, cắt sẽ không
Janus Troelsen

Ngoài việc làm việc đúng cách với máy phát điện và các trình vòng lặp không thể cắt khác, giải pháp đầu tiên cũng không yêu cầu giá trị "phụ" nếu phần cuối cùng nhỏ hơn size, đôi khi là mong muốn.
dano

1
Cũng +1 cho máy phát điện. Các giải pháp khác yêu cầu một lencuộc gọi và do đó không hoạt động trên các máy phát điện khác.
Cuadue


11

Giải pháp lý tưởng cho vấn đề này hoạt động với các trình vòng lặp (không chỉ các chuỗi). Nó cũng nên được nhanh chóng.

Đây là giải pháp được cung cấp bởi tài liệu cho itertools:

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

Sử dụng ipython %timeittrên không khí cuốn sách mac của tôi, tôi nhận được 47,5 chúng tôi mỗi vòng.

Tuy nhiên, điều này thực sự không hiệu quả đối với tôi vì kết quả được đệm thành các nhóm thậm chí có kích thước. Một giải pháp mà không có phần đệm thì phức tạp hơn một chút. Giải pháp ngây thơ nhất có thể là:

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

Đơn giản, nhưng khá chậm: 693 chúng tôi mỗi vòng lặp

Giải pháp tốt nhất tôi có thể đưa ra với việc sử dụng islicecho vòng lặp bên trong:

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

Với cùng một bộ dữ liệu, tôi nhận được 305 chúng tôi mỗi vòng lặp.

Không thể có được một giải pháp thuần túy nhanh hơn thế, tôi cung cấp giải pháp sau đây với một cảnh báo quan trọng: Nếu dữ liệu đầu vào của bạn có các trường hợp filldatatrong đó, bạn có thể nhận được câu trả lời sai.

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

Tôi thực sự không thích câu trả lời này, nhưng nó nhanh hơn đáng kể. 124 chúng tôi mỗi vòng lặp


Bạn có thể giảm thời gian chạy cho công thức # 3 xuống ~ 10-15% bằng cách di chuyển nó sang lớp C (bỏ itertoolsnhập, mapphải là Py3 maphoặc imap) : def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). Hàm cuối cùng của bạn có thể được làm cho ít giòn hơn bằng cách sử dụng một sentinel: thoát khỏi fillvalueđối số; thêm một dòng đầu tiên fillvalue = object(), sau đó thay đổi ifkiểm tra if i[-1] is fillvalue:và dòng nó điều khiển yield tuple(v for v in i if v is not fillvalue). Đảm bảo không có giá trị trong iterablecó thể bị nhầm lẫn với giá trị phụ.
ShadowRanger

BTW, ngón tay cái lớn lên trên # 4. Tôi đã định đăng bài tối ưu hóa số 3 của mình dưới dạng câu trả lời tốt hơn (hiệu suất khôn ngoan) so với những gì đã được đăng từ trước đến nay, nhưng với tinh chỉnh để làm cho nó đáng tin cậy, khả năng phục hồi số 4 chạy nhanh gấp đôi so với tối ưu hóa số 3; Tôi không mong đợi một giải pháp với các vòng lặp mức Python (và không có sự khác biệt về thuật toán lý thuyết AFAICT) để giành chiến thắng. Tôi giả sử # 3 thua do chi phí xây dựng / lặp isliceđối tượng (số 3 thắng nếu ntương đối lớn, ví dụ số lượng nhóm nhỏ, nhưng đó là tối ưu hóa cho trường hợp không phổ biến), nhưng tôi không hy vọng nó sẽ hoàn toàn cực.
ShadowRanger

Đối với số 4, nhánh đầu tiên của điều kiện chỉ được thực hiện ở lần lặp cuối cùng (bộ dữ liệu cuối cùng). Thay vì hoàn nguyên lại bộ dữ liệu cuối cùng một lần nữa, hãy lưu bộ đệm modulo theo chiều dài của lần lặp ban đầu ở trên cùng và sử dụng nó để cắt bỏ phần đệm không mong muốn từ izip_longesttrên bộ cuối cùng : yield i[:modulo]. Ngoài ra, đối với argsbiến, tuple nó thay vì một danh sách : args = (iter(iterable),) * n. Cạo thêm một vài chu kỳ đồng hồ tắt. Cuối cùng, nếu chúng ta bỏ qua fillvalue và giả định None, điều kiện có thể trở thành if None in icho nhiều chu kỳ đồng hồ hơn.
Kumba

1
@Kumba: Đề xuất đầu tiên của bạn giả sử đầu vào có độ dài đã biết. Nếu đó là một trình vòng lặp / trình tạo, không phải là một bộ sưu tập có độ dài đã biết, thì không có gì để lưu vào bộ đệm. Không có lý do thực sự để sử dụng tối ưu hóa như vậy dù sao; bạn đang tối ưu hóa trường hợp không phổ biến (cuối cùng yield), trong khi trường hợp phổ biến không bị ảnh hưởng.
ShadowRanger

10

Tôi cần một giải pháp cũng sẽ làm việc với các bộ và máy phát điện. Tôi không thể nghĩ ra bất cứ thứ gì rất ngắn và đẹp, nhưng ít nhất nó cũng dễ đọc.

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

Danh sách:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Bộ:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Máy phát điện:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

8

Tương tự như các đề xuất khác, nhưng không hoàn toàn giống hệt nhau, tôi thích thực hiện theo cách này, vì nó đơn giản và dễ đọc:

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

Bằng cách này, bạn sẽ không nhận được phần cuối cùng. Nếu bạn muốn lấy (9, None, None, None)đoạn cuối cùng, chỉ cần sử dụng izip_longesttừ itertools.


có thể được cải thiện vớizip(*([it]*4))
Jean-François Fabre

@ Jean-François Fabre: từ quan điểm dễ đọc, tôi không thấy đó là một cải tiến. Và nó cũng chậm hơn một chút. Đó là một sự cải thiện nếu bạn đang chơi golf, điều mà tôi thì không.
kriss

không tôi không chơi gôn, nhưng nếu bạn có 10 đối số thì sao? Tôi đã đọc cấu trúc đó trong một số trang chính thức. Nhưng tất nhiên tôi dường như không thể tìm thấy nó ngay bây giờ :)
Jean-François Fabre

@ Jean-François Fabre: nếu tôi có 10 đối số hoặc số lượng đối số thay đổi, đó là một tùy chọn, nhưng tôi muốn viết: zip (* (nó,) * 10)
kriss

đúng! đó là những gì tôi đọc. không phải những thứ trong danh sách mà tôi đã tạo nên :)
Jean-François Fabre

8

Nếu bạn không phiền khi sử dụng gói bên ngoài, bạn có thể sử dụng iteration_utilities.groupertừ 1 . Nó hỗ trợ tất cả các lần lặp (không chỉ các chuỗi):iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

bản in nào:

(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

Trong trường hợp độ dài không phải là bội số của nhóm, nó cũng hỗ trợ điền (nhóm cuối chưa hoàn thành) hoặc cắt bớt (loại bỏ nhóm cuối chưa hoàn thành) nhóm cuối cùng:

from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

Điểm chuẩn

Tôi cũng quyết định so sánh thời gian chạy của một vài phương pháp được đề cập. Đó là một biểu đồ log-log nhóm thành các nhóm yếu tố "10" dựa trên một danh sách có kích thước khác nhau. Đối với kết quả định tính: Thấp hơn có nghĩa là nhanh hơn:

nhập mô tả hình ảnh ở đây

Ít nhất trong tiêu chuẩn này iteration_utilities.grouperthực hiện tốt nhất. Tiếp theo là cách tiếp cận của Craz .

Điểm chuẩn được tạo với 1 . Mã được sử dụng để chạy điểm chuẩn này là:simple_benchmark

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của các thư viện iteration_utilitiessimple_benchmark.


7

Vì không ai đề cập đến nó nên đây là một zip()giải pháp:

>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

Nó chỉ hoạt động nếu chiều dài của chuỗi của bạn luôn chia hết cho kích thước khối hoặc bạn không quan tâm đến một đoạn cuối nếu không.

Thí dụ:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

Hoặc sử dụng itertools.izip để trả về một trình vòng lặp thay vì danh sách:

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

Đệm có thể được sửa bằng cách sử dụng câu trả lời của @ ΤΖΩΤΖΙΟΥ :

>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)

5

Sử dụng map () thay vì zip () khắc phục sự cố đệm trong câu trả lời của JF Sebastian:

>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

Thí dụ:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

2
Điều này được xử lý tốt hơn với itertools.izip_longest(Py2) / itertools.zip_longest(Py3); việc sử dụng mapnày không được chấp nhận gấp đôi và không khả dụng trong Py3 (bạn không thể vượt qua Nonenhư chức năng của trình ánh xạ và nó dừng lại khi hết vòng lặp ngắn nhất, không phải là lâu nhất; nó không phải là pad).
ShadowRanger

4

Một cách tiếp cận khác là sử dụng hình thức hai đối số iter:

from itertools import islice

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

Điều này có thể được điều chỉnh dễ dàng để sử dụng phần đệm (điều này tương tự như câu trả lời của Markus Jarderotùi ):

from itertools import islice, chain, repeat

def group_pad(it, size, pad=None):
    it = chain(iter(it), repeat(pad))
    return iter(lambda: tuple(islice(it, size)), (pad,) * size)

Chúng thậm chí có thể được kết hợp cho phần đệm tùy chọn:

_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

1
tốt hơn bởi vì bạn có tùy chọn bỏ qua phần đệm!
n611x007

3

Nếu danh sách lớn, cách hiệu quả cao nhất để làm điều này sẽ là sử dụng trình tạo:

def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
    print x

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)

(Tôi nghĩ rằng đề xuất itertools của MizardX có chức năng tương đương với điều này.)
Robert Rossney

1
(Trên thực tế, về sự phản chiếu, không tôi không. Itertools.islice trả về một trình vòng lặp, nhưng nó không sử dụng một trình duyệt hiện có.)
Robert Rossney

Nó rất hay và đơn giản, nhưng vì một số lý do ngay cả khi không chuyển đổi thành chậm hơn 4-7 lần so với phương pháp cá mú được chấp nhận trên iterable = range(100000000)& chunksizelên tới 10000.
Valentas

Tuy nhiên, nói chung tôi muốn giới thiệu phương pháp này, bởi vì một trong những được chấp nhận có thể cực kỳ chậm khi kiểm tra cho mục cuối cùng là chậm docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

Sử dụng ít chức năng và những thứ thực sự không hấp dẫn tôi; Tôi thích chỉ sử dụng lát:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...

tốt đẹp nhưng không tốt cho một luồng không xác định mà không được biết đến len. bạn có thể làm một bài kiểm tra với itertools.repeathoặc itertools.cycle.
n611x007

1
Ngoài ra, ăn hết bộ nhớ vì sử dụng [...for...] khả năng hiểu danh sách để xây dựng danh sách vật lý thay vì sử dụng (...for...) biểu thức trình tạo chỉ quan tâm đến phần tử tiếp theo và bộ nhớ dự phòng
n611x007

2

Để tránh tất cả các chuyển đổi vào một danh sách import itertoolsvà:

>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

Sản xuất:

... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

Tôi đã kiểm tra groupbyvà nó không chuyển đổi thành danh sách hoặc sử dụng lenvì vậy tôi (nghĩ) điều này sẽ trì hoãn độ phân giải của từng giá trị cho đến khi nó thực sự được sử dụng. Đáng buồn thay, không có câu trả lời có sẵn (tại thời điểm này) dường như cung cấp biến thể này.

Rõ ràng nếu bạn cần xử lý lần lượt từng mục lồng một vòng lặp for trên g:

for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

Mối quan tâm cụ thể của tôi về điều này là nhu cầu tiêu thụ một trình tạo để gửi các thay đổi theo lô lên tới 1000 cho API gmail:

    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)

Điều gì sẽ xảy ra nếu danh sách bạn đang chunking là một cái gì đó khác với một chuỗi các số nguyên tăng dần?
PaulMcG

@PaulMcGuire thấy groupby ; đưa ra một hàm để mô tả thứ tự sau đó các phần tử của iterable có thể là bất cứ thứ gì, phải không?
John Mee

1
Vâng, tôi quen thuộc với nhóm. Nhưng nếu các tin nhắn là các chữ cái "ABCDEFG", thì groupby(messages, lambda x: x/3)sẽ cung cấp cho bạn TypeError (vì đã cố chia một chuỗi cho một int), chứ không phải các nhóm 3 chữ cái. Bây giờ nếu bạn đã làm groupby(enumerate(messages), lambda x: x[0]/3)bạn có thể có một cái gì đó. Nhưng bạn đã không nói điều đó trong bài viết của bạn.
PaulMcG

2

Với NumPy, điều đó thật đơn giản:

ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

đầu ra:

1 2
3 4
5 6
7 8

2
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()

2

Trừ khi tôi bỏ lỡ điều gì đó, giải pháp đơn giản sau đây với các biểu thức trình tạo chưa được đề cập. Nó giả định rằng cả kích thước và số lượng khối được biết đến (thường là trường hợp này) và không yêu cầu đệm:

def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))

1

Trong phương pháp thứ hai của bạn, tôi sẽ tiến tới nhóm 4 người tiếp theo bằng cách này:

ints = ints[4:]

Tuy nhiên, tôi chưa thực hiện bất kỳ phép đo hiệu suất nào vì vậy tôi không biết cái nào có thể hiệu quả hơn.

Có nói rằng, tôi thường sẽ chọn phương pháp đầu tiên. Nó không đẹp, nhưng đó thường là hậu quả của việc giao tiếp với thế giới bên ngoài.


1

Một câu trả lời khác, những lợi thế của nó là:

1) Dễ hiểu
2) Hoạt động trên bất kỳ lần lặp nào, không chỉ các chuỗi (một số câu trả lời ở trên sẽ gây nghẹt thở cho tập tin)
3) Không tải chunk vào bộ nhớ cùng một lúc
4) Không tạo danh sách tham chiếu dài cùng một trình vòng lặp trong bộ nhớ
5) Không có phần đệm của các giá trị điền vào cuối danh sách

Điều đó đang được nói, tôi đã không tính thời gian để nó có thể chậm hơn một số phương pháp thông minh hơn và một số ưu điểm có thể không liên quan trong trường hợp sử dụng.

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

Cập nhật:
Một vài hạn chế do thực tế các vòng lặp bên trong và bên ngoài đang kéo các giá trị từ cùng một trình vòng lặp:
1) tiếp tục không hoạt động như mong đợi trong vòng lặp bên ngoài - nó chỉ tiếp tục với mục tiếp theo thay vì bỏ qua một đoạn . Tuy nhiên, điều này có vẻ không thành vấn đề vì không có gì để kiểm tra ở vòng ngoài.
2) phá vỡ không hoạt động như mong đợi trong vòng lặp bên trong - điều khiển sẽ cuộn lại trong vòng lặp bên trong với mục tiếp theo trong trình vòng lặp. Để bỏ qua toàn bộ khối, hãy bọc iterator bên trong (ii ở trên) trong một tuple, ví dụ for c in tuple(ii), hoặc đặt cờ và xả hết iterator.


1
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist

+1 nó bỏ qua phần đệm; bạn và bcoughlan rất giống
n611x007

1

Bạn có thể sử dụng chức năng phân vùng hoặc khối từ thư viện funcy :

from funcy import partition

for a, b, c, d in partition(4, ints):
    foo += a * b * c * d

Các chức năng này cũng có các phiên bản lặp ipartitionichunkssẽ hiệu quả hơn trong trường hợp này.

Bạn cũng có thể nhìn trộm thực hiện của họ .


1

Về giải pháp được đưa ra J.F. Sebastian ở đây :

def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

Đó là thông minh, nhưng có một nhược điểm - luôn luôn trả lại tuple. Làm thế nào để có được chuỗi thay thế?
Tất nhiên bạn có thể viết''.join(chunker(...)) , nhưng bộ tuple tạm thời được xây dựng bằng mọi cách.

Bạn có thể thoát khỏi bộ dữ liệu tạm thời bằng cách viết riêng zip, như thế này:

class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

Sau đó

def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

Ví dụ sử dụng:

>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'

2
Không phải là một bài phê bình có nghĩa là để bạn thay đổi câu trả lời của bạn, mà là một nhận xét: Mã là một trách nhiệm pháp lý. Càng nhiều mã bạn viết, bạn càng tạo nhiều không gian cho các lỗi để ẩn. Từ quan điểm này, viết lại zipthay vì sử dụng cái hiện có dường như không phải là ý tưởng tốt nhất.
Alfe

1

Tôi thích cách tiếp cận này. Nó cảm thấy đơn giản và không kỳ diệu và hỗ trợ tất cả các loại lặp và không yêu cầu nhập khẩu.

def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk

1

Tôi không bao giờ muốn phần đệm của mình được đệm, vì vậy yêu cầu đó là cần thiết. Tôi thấy rằng khả năng làm việc trên bất kỳ lần lặp nào cũng là yêu cầu. Do đó, tôi đã quyết định gia hạn câu trả lời được chấp nhận, https://stackoverflow.com/a/434411/1074659 .

Hiệu suất có một cú đánh nhẹ trong phương pháp này nếu không muốn đệm do cần so sánh và lọc các giá trị đệm. Tuy nhiên, đối với kích thước khối lớn, tiện ích này rất hiệu quả.

#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks

1

Đây là một chunker không có nhập khẩu hỗ trợ máy phát điện:

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

Ví dụ sử dụng:

>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]

1

Với Python 3.8, bạn có thể sử dụng toán tử hải mã và itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 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, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

0

Dường như không có một cách hay để làm điều này. Đây là một trang có một số phương pháp, bao gồm:

def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq

0

Nếu các danh sách có cùng kích thước, bạn có thể kết hợp chúng thành các danh sách gồm 4 bộ zip(). Ví dụ:

# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

Đây là những gì zip()hàm tạo ra:

>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

Nếu danh sách lớn và bạn không muốn kết hợp chúng thành một danh sách lớn hơn, hãy sử dụng itertools.izip(), nó tạo ra một trình vòng lặp, thay vì danh sách.

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...

0

Giải pháp một lớp lót, adhoc để lặp lại một danh sách xtheo từng khối kích thước 4-

for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
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.