Làm thế nào để bạn chia một danh sách thành các phần có kích thước đồng đều?


2267

Tôi có một danh sách các chiều dài tùy ý, và tôi cần chia nó thành các phần có kích thước bằng nhau và vận hành trên nó. Có một số cách rõ ràng để làm điều này, như giữ một bộ đếm và hai danh sách, và khi danh sách thứ hai đầy, hãy thêm nó vào danh sách đầu tiên và làm trống danh sách thứ hai cho vòng dữ liệu tiếp theo, nhưng điều này cực kỳ tốn kém.

Tôi đã tự hỏi nếu có ai có một giải pháp tốt cho điều này cho các danh sách có độ dài bất kỳ, ví dụ như sử dụng máy phát điện.

Tôi đang tìm kiếm thứ gì đó hữu ích itertoolsnhưng tôi không thể tìm thấy thứ gì rõ ràng hữu ích. Có thể đã bỏ lỡ nó, mặc dù.

Câu hỏi liên quan: Cách thức pythonic nhất của người Hồi giáo để lặp đi lặp lại qua một danh sách trong khối là gì?


1
Trước khi bạn đăng câu trả lời mới, hãy xem xét đã có hơn 60 câu trả lời cho câu hỏi này. Xin vui lòng, đảm bảo rằng câu trả lời của bạn đóng góp thông tin không nằm trong số các câu trả lời hiện có.
janniks

Đối với người dùng muốn tránh một đoạn cuối nhỏ tùy ý, hãy xem qua Chia tách danh sách thành N phần có độ dài xấp xỉ bằng nhau
wim

Câu trả lời:


3151

Đây là một trình tạo mang lại các khối bạn muốn:

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

import pprint
pprint.pprint(list(chunks(range(10, 75), 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]]

Nếu bạn đang sử dụng Python 2, bạn nên sử dụng xrange()thay vì range():

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in xrange(0, len(lst), n):
        yield lst[i:i + n]

Ngoài ra, bạn có thể chỉ cần sử dụng khả năng hiểu danh sách thay vì viết một hàm, mặc dù vậy, nên đóng gói các hoạt động như thế này trong các hàm được đặt tên để mã của bạn dễ hiểu hơn. Con trăn 3:

[lst[i:i + n] for i in range(0, len(lst), n)]

Phiên bản Python 2:

[lst[i:i + n] for i in xrange(0, len(lst), n)]

72
Điều gì xảy ra nếu chúng ta không thể cho biết độ dài của danh sách? Hãy thử điều này trên itertools.repeat ([1, 2, 3]), ví dụ:
jespern

47
Đó là một phần mở rộng thú vị cho câu hỏi, nhưng câu hỏi ban đầu rõ ràng đã hỏi về hoạt động trong danh sách.
Ned Batchelder

33
các chức năng này cần phải có trong thư viện tiêu chuẩn chết tiệt
dgan

6
@Calimo: bạn đề nghị gì? Tôi đưa cho bạn một danh sách với 47 yếu tố. Làm thế nào bạn muốn chia nó thành "khối có kích thước đồng đều"? OP chấp nhận câu trả lời, vì vậy họ rõ ràng là ổn với đoạn có kích thước khác nhau cuối cùng. Có lẽ cụm từ tiếng Anh là không chính xác?
Ned Batchelder

8
Vui lòng không đặt tên cho biến của bạn, nó trông giống hệt 1 và khó hiểu. Mọi người đang sao chép mã của bạn và nghĩ rằng điều này là ổn.
Yasen

555

Nếu bạn muốn một cái gì đó siêu đơn giản:

def chunks(l, n):
    n = max(1, n)
    return (l[i:i+n] for i in range(0, len(l), n))

Sử dụng xrange()thay vìrange() trong trường hợp Python 2.x


6
Hoặc (nếu chúng tôi thực hiện các cách biểu diễn khác nhau của chức năng cụ thể này), bạn có thể xác định hàm lambda thông qua: lambda x, y: [x [i: i + y] cho i trong phạm vi (0, len (x), y) ]. Tôi thích phương pháp hiểu danh sách này!
JP

4
sau khi trở về phải có [, không (
alwbtc

2
"Siêu đơn giản" có nghĩa là không phải gỡ lỗi các vòng lặp vô hạn - kudos cho max().
Bob Stein

không có gì đơn giản về giải pháp này
mit

1
@Nhoj_Gonk Rất tiếc, đó không phải là một vòng lặp vô hạn, nhưng các khối (L, 0) sẽ tăng ValueError mà không có max (). Thay vào đó, max () biến mọi thứ nhỏ hơn 1 thành 1.
Bob Stein

295

Trực tiếp từ tài liệu Python (cũ) (công thức nấu ăn cho itertools):

from itertools import izip, chain, repeat

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

Phiên bản hiện tại, theo đề xuất của JFSebastian:

#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

Tôi đoán cỗ máy thời gian của Guido hoạt động tốt.

Các giải pháp này hoạt động vì [iter(iterable)]*n(hoặc tương đương trong phiên bản trước) tạo một lần lặp, lặp lại nhiều nlần trong danh sách. izip_longestsau đó thực hiện một cách hiệu quả một vòng lặp của "mỗi" vòng lặp; bởi vì đây là cùng một trình vòng lặp, nên nó được nâng cao theo từng cuộc gọi như vậy, dẫn đến mỗi lần quay vòng zip như vậy tạo ra một bộ nvật phẩm.


@ninjagecko: list(grouper(3, range(10)))trả về [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]và tất cả các bộ dữ liệu có độ dài 3. Vui lòng giải thích về nhận xét của bạn vì tôi không thể hiểu được; Bạn gọi một vật và làm thế nào để bạn xác định nó là bội số của 3 trong Trò chơi kỳ vọng thứ của bạn sẽ là bội số của 3 Lần? Cảm ơn bạn trước.
tzot

14
nâng cấp điều này bởi vì nó hoạt động trên các trình tạo (không có len) và sử dụng mô đun itertools thường nhanh hơn.
Michael Dillon

88
Một ví dụ kinh điển về itertoolsphương pháp tiếp cận chức năng lạ mắt tạo ra một số bùn không thể đọc được, khi so sánh với việc thực hiện trăn thuần túy đơn giản và ngây thơ
wim

15
@wim Cho rằng câu trả lời này bắt đầu như một đoạn trích từ tài liệu Python, tôi khuyên bạn nên mở một vấn đề trên bug.python.org .
tzot

1
@pedrosaurio nếu l==[1, 2, 3]sau đó f(*l)tương đương với f(1, 2, 3). Xem câu hỏi đócác tài liệu chính thức .
tzot

225

Tôi biết đây là loại cũ nhưng chưa ai đề cập numpy.array_split:

import numpy as np

lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
#  array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
#  array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
#  array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
#  array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]

12
Điều này cho phép bạn đặt tổng số khối, không phải số phần tử trên mỗi khối.
FizxMike

6
bạn có thể tự làm toán nếu bạn có 10 phần tử, bạn có thể nhóm chúng thành 2, 5 phần tử khối hoặc năm phần tử 2 phần tử
Moj

24
+1 Đây là giải pháp yêu thích của tôi, vì nó chia mảng thành các mảng có kích thước bằng nhau , trong khi các giải pháp khác thì không (trong tất cả các giải pháp khác tôi đã xem xét, mảng cuối cùng có thể nhỏ tùy ý).
MiniQuark

@MiniQuark nhưng điều này sẽ làm gì khi số lượng khối không phải là một yếu tố của kích thước mảng ban đầu?
Baldrickk

1
@Baldrickk Nếu bạn chia N phần tử thành các phần K, thì phần N% K đầu tiên sẽ có phần tử N // K + 1 và phần còn lại sẽ có phần tử N // K. Ví dụ: nếu bạn chia một mảng chứa 108 phần tử thành 5 phần, thì phần 108% 5 = 3 đầu tiên sẽ chứa 108 // 5 + 1 = 22 phần tử và phần còn lại của các phần sẽ có 108 // 5 = 21 các yếu tố.
MiniQuark

147

Tôi không là ai cả ngạc nhiên đã nghĩ đến việc sử dụng iterhình thức hai đối số :

from itertools import islice

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

Bản giới thiệu:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]

Điều này làm việc với bất kỳ lặp đi lặp lại và tạo ra đầu ra một cách lười biếng. Nó trả về các bộ dữ liệu thay vì các bộ lặp, nhưng tôi nghĩ rằng dù sao nó cũng có một sự tao nhã nhất định. Nó cũng không pad; Nếu bạn muốn đệm, một biến thể đơn giản ở trên sẽ đủ:

from itertools import islice, chain, repeat

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

Bản giới thiệu:

>>> list(chunk_pad(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk_pad(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Giống như các izip_longestgiải pháp dựa trên, các miếng đệm luôn luôn ở trên . Theo như tôi biết, không có công thức itertools một hoặc hai dòng cho một chức năng tùy chọn đệm. Bằng cách kết hợp hai cách tiếp cận trên, cách tiếp cận này khá gần:

_no_padding = object()

def chunk(it, size, padval=_no_padding):
    if padval == _no_padding:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(padval))
        sentinel = (padval,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

Bản giới thiệu:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
>>> list(chunk(range(14), 3, None))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Tôi tin rằng đây là chương trình rút gọn ngắn nhất được đề xuất cung cấp phần đệm tùy chọn.

Như Tomasz Gandor đã quan sát , hai bộ đệm đệm sẽ dừng đột ngột nếu chúng gặp phải một chuỗi dài các giá trị pad. Đây là một biến thể cuối cùng giải quyết vấn đề đó một cách hợp lý:

_no_padding = object()
def chunk(it, size, padval=_no_padding):
    it = iter(it)
    chunker = iter(lambda: tuple(islice(it, size)), ())
    if padval == _no_padding:
        yield from chunker
    else:
        for ch in chunker:
            yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))

Bản giới thiệu:

>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]

7
Thật tuyệt vời, phiên bản đơn giản của bạn là yêu thích của tôi. Những người khác cũng đã đưa ra islice(it, size)biểu thức cơ bản và nhúng nó (như tôi đã làm) trong một cấu trúc vòng lặp. Chỉ có bạn nghĩ về phiên bản hai đối số của iter()(tôi hoàn toàn không biết), điều này làm cho nó siêu thanh lịch (và có lẽ hiệu quả nhất về hiệu suất). Tôi không có ý tưởng rằng đối số đầu tiên iterthay đổi thành hàm 0 đối số khi được đưa ra trọng điểm. Bạn trả về một iterator (pot. Infinite) của chunk, có thể sử dụng một iterator (pot. Infinite) làm đầu vào, không có len()và không có các lát mảng. Tuyệt vời!
ThomasH

1
Đây là lý do tại sao tôi đọc qua các câu trả lời thay vì chỉ quét cặp đôi hàng đầu. Tùy chọn đệm là một yêu cầu trong trường hợp của tôi và tôi cũng đã tìm hiểu về hình thức hai đối số của iter.
Kerr

Tôi đã ủng hộ điều này, nhưng vẫn vậy - chúng ta đừng vượt qua nó! Trước hết, lambda có thể là xấu (đóng cửa chậm trên ititerator. Thứ hai, và hầu hết nhập khẩu - bạn sẽ kết thúc sớm nếu một đoạn padvalthực sự tồn tại trong vòng lặp của bạn, và nên được xử lý.
Tomasz Gandor 16/11/18

@TomaszGandor, tôi lấy điểm đầu tiên của bạn! Mặc dù hiểu biết của tôi là lambda không chậm hơn chức năng thông thường, tất nhiên bạn đúng rằng chức năng gọi và tra cứu đóng sẽ làm chậm điều này. Tôi không biết hiệu suất tương đối của việc này sẽ là gì so với izip_longestcách tiếp cận, chẳng hạn - tôi nghi ngờ đây có thể là một sự đánh đổi phức tạp. Nhưng ... không phải padvalvấn đề được chia sẻ bởi mọi câu trả lời ở đây có cung cấp padvaltham số không?
gửi

1
@TomaszGandor, đủ công bằng! Nhưng nó không quá khó để tạo ra một phiên bản sửa lỗi này. (Ngoài ra, lưu ý rằng phiên bản đầu tiên, trong đó sử dụng ()như là trọng điểm, thực hiện công việc một cách chính xác này là do. tuple(islice(it, size))Sản lượng ()khi itcó sản phẩm nào.)
senderle

93

Đây là một trình tạo hoạt động trên các vòng lặp tùy ý:

def split_seq(iterable, size):
    it = iter(iterable)
    item = list(itertools.islice(it, size))
    while item:
        yield item
        item = list(itertools.islice(it, size))

Thí dụ:

>>> import pprint
>>> pprint.pprint(list(split_seq(xrange(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

52
def chunk(input, size):
    return map(None, *([iter(input)] * size))

map(None, iter)bằng izip_longest(iter).
Thomas Ahle

1
@TomaszWysocki Bạn có thể giải thích *trước mặt bạn lặp tuple không? Có thể trong văn bản câu trả lời của bạn, nhưng tôi đã lưu ý rằng đã *sử dụng cách đó trong Python trước đây. Cảm ơn!
theJollySin

1
@theJollySin Trong ngữ cảnh này, nó được gọi là toán tử splat. Việc sử dụng nó được giải thích ở đây - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms

2
Đóng nhưng đoạn cuối không có phần tử nào để điền vào. Điều này có thể hoặc không thể là một khiếm khuyết. Mô hình thực sự mát mẻ mặc dù.

49

Đơn giản mà thanh lịch

l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]

hoặc nếu bạn thích:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)

18
Ngươi sẽ không đặt tên cho một biến số giống như số Ả Rập. Trong một số phông chữ, 1lkhông thể phân biệt. Như là 0O. Và đôi khi thậm chí I1.
Alfe

14
@ Phông chữ bị lỗi. Mọi người không nên sử dụng các phông chữ như vậy. Không cho lập trình, không cho bất cứ điều gì .
Jerry B

17
Lambdas có nghĩa là được sử dụng như các chức năng không tên. Không có điểm nào trong việc sử dụng chúng như thế. Ngoài ra, nó làm cho việc gỡ lỗi trở nên khó khăn hơn vì trac trở lại sẽ báo cáo "trong <lambda>" thay vì "trong khối" trong trường hợp có lỗi. Tôi chúc bạn may mắn tìm ra một vấn đề nếu bạn có cả đống thứ này :)
Chris Koston

1
nó phải là 0 chứ không phải 1 bên trong xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta 28/12/13

LƯU Ý: Đối với người dùng Python 3 sử dụng range.
Christian Dean

40

Phê bình các câu trả lời khác ở đây:

Không có câu trả lời nào trong số này là các đoạn có kích thước bằng nhau, tất cả đều để lại một đoạn thô sơ ở cuối, vì vậy chúng không hoàn toàn cân bằng. Nếu bạn đang sử dụng các chức năng này để phân phối công việc, bạn đã có triển vọng về một khả năng hoàn thành tốt trước các chức năng khác, vì vậy nó sẽ không làm gì trong khi những người khác tiếp tục làm việc chăm chỉ.

Ví dụ: câu trả lời hàng đầu hiện tại kết thúc bằng:

[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]

Tôi chỉ ghét cái runt đó vào cuối!

Những người khác, thích list(grouper(3, xrange(7))), và chunk(xrange(7), 3)cả hai trở về : [(0, 1, 2), (3, 4, 5), (6, None, None)]. Các None's chỉ là đệm, và khá thanh nha theo ý kiến của tôi. Họ KHÔNG đồng đều chunk các iterables.

Tại sao chúng ta không thể phân chia những thứ này tốt hơn?

Giải pháp của tôi

Dưới đây là một giải pháp cân bằng, chuyển thể từ một hàm tôi đã sử dụng trong sản xuất (Lưu ý trong Python 3 để thay thế xrangevới range):

def baskets_from(items, maxbaskets=25):
    baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range
    for i, item in enumerate(items):
        baskets[i % maxbaskets].append(item)
    return filter(None, baskets) 

Và tôi đã tạo một trình tạo tương tự nếu bạn đưa nó vào danh sách:

def iter_baskets_from(items, maxbaskets=3):
    '''generates evenly balanced baskets from indexable iterable'''
    item_count = len(items)
    baskets = min(item_count, maxbaskets)
    for x_i in xrange(baskets):
        yield [items[y_i] for y_i in xrange(x_i, item_count, baskets)]

Và cuối cùng, vì tôi thấy rằng tất cả các hàm trên trả về các phần tử theo thứ tự liền kề (như chúng đã được đưa ra):

def iter_baskets_contiguous(items, maxbaskets=3, item_count=None):
    '''
    generates balanced baskets from iterable, contiguous contents
    provide item_count if providing a iterator that doesn't support len()
    '''
    item_count = item_count or len(items)
    baskets = min(item_count, maxbaskets)
    items = iter(items)
    floor = item_count // baskets 
    ceiling = floor + 1
    stepdown = item_count % baskets
    for x_i in xrange(baskets):
        length = ceiling if x_i < stepdown else floor
        yield [items.next() for _ in xrange(length)]

Đầu ra

Để kiểm tra chúng:

print(baskets_from(xrange(6), 8))
print(list(iter_baskets_from(xrange(6), 8)))
print(list(iter_baskets_contiguous(xrange(6), 8)))
print(baskets_from(xrange(22), 8))
print(list(iter_baskets_from(xrange(22), 8)))
print(list(iter_baskets_contiguous(xrange(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(xrange(26), 5))
print(list(iter_baskets_from(xrange(26), 5)))
print(list(iter_baskets_contiguous(xrange(26), 5)))

Mà in ra:

[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'B', 'C'], ['D', 'E'], ['F', 'G']]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Lưu ý rằng trình tạo liền kề cung cấp các khối theo các mẫu có cùng độ dài với hai mẫu kia, nhưng các mục đều theo thứ tự và chúng được chia đều vì người ta có thể chia một danh sách các phần tử rời rạc.


Bạn nói rằng không có cái nào ở trên cung cấp các khối có kích thước bằng nhau. Nhưng cái này thì có, cái này cũng vậy .
gửi

1
@senderle, Cái đầu tiên list(grouper(3, xrange(7))), và cái thứ hai, chunk(xrange(7), 3)cả hai đều trả về : [(0, 1, 2), (3, 4, 5), (6, None, None)]. Các None's chỉ là đệm, và khá thanh nha theo ý kiến của tôi. Họ KHÔNG đồng đều chunk các iterables. Cảm ơn bạn ủng hộ!
Aaron Hall

4
Bạn đưa ra câu hỏi (không thực hiện một cách rõ ràng, vì vậy tôi làm điều đó ngay bây giờ ở đây) cho dù các khối có kích thước bằng nhau (ngoại trừ phần cuối cùng, nếu không thể) hoặc liệu một kết quả cân bằng (càng tốt càng tốt) sẽ thường xuyên hơn những gì sẽ cần. Bạn cho rằng giải pháp cân bằng là thích; điều này có thể đúng nếu những gì bạn lập trình gần với thế giới thực (ví dụ: thuật toán xử lý thẻ cho trò chơi thẻ mô phỏng). Trong các trường hợp khác (như điền dòng bằng từ) người ta sẽ thích giữ các dòng đầy đủ nhất có thể. Vì vậy, tôi không thể thực sự thích cái này hơn cái kia; chúng chỉ dành cho các trường hợp sử dụng khác nhau.
Alfe

@ ChristopherBarrington-Leigh Điểm tốt, cho DataFrames, bạn nên có lẽ sử dụng lát, vì tôi tin rằng DataFrame đối tượng không thường sao chép trên cắt, ví dụ:import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron sảnh

1
@AaronHall Rất tiếc. Tôi đã xóa bình luận của mình vì tôi đã đoán được bài phê bình của mình, nhưng bạn đã nhanh chóng rút ra. Cảm ơn! Trong thực tế, tuyên bố của tôi rằng nó không hoạt động cho dataframes là đúng. Nếu các mục là một khung dữ liệu, chỉ cần sử dụng các mục năng suất [phạm vi (x_i, item_count, giỏ)] làm dòng cuối cùng. Tôi đã đưa ra một câu trả lời riêng (nhưng khác), trong đó bạn chỉ định kích thước nhóm (tối thiểu) mong muốn.
CPBL

38

Tôi đã thấy câu trả lời Python-ish tuyệt vời nhất trong một bản sao của câu hỏi này:

from itertools import zip_longest

a = range(1, 16)
i = iter(a)
r = list(zip_longest(i, i, i))
>>> print(r)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

Bạn có thể tạo n-tuple cho bất kỳ n. Nếu a = range(1, 15), thì kết quả sẽ là:

[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]

Nếu danh sách được chia đều, thì bạn có thể thay thế zip_longestbằng zip, nếu không bộ ba (13, 14, None)sẽ bị mất. Python 3 được sử dụng ở trên. Đối với Python 2, sử dụng izip_longest.


thật tuyệt nếu danh sách và khối của bạn ngắn, làm thế nào bạn có thể điều chỉnh nó để chia danh sách của bạn thành 1000 khối? bạn sẽ không mã zip (i, i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith

9
zip(i, i, i, ... i)tất nhiên, với các đối số "chunk_size" thành zip () có thể được viết là zip(*[i]*chunk_size)Cho dù đó có phải là một ý tưởng tốt hay không thì vẫn còn gây tranh cãi.
Wilson F

1
Nhược điểm của điều này là nếu bạn không chia đều, bạn sẽ bỏ các phần tử, vì zip dừng ở mức lặp ngắn nhất - & izip_longest sẽ thêm các phần tử mặc định.
Aaron Hall

zip_longestnên được sử dụng, như được thực hiện trong: stackoverflow.com/a/434411/1959808
Ioannis Filippidis

Câu trả lời với range(1, 15)đã thiếu các yếu tố, bởi vì có 14 yếu tố range(1, 15), chứ không phải 15.
Ioannis Filippidis

35

Nếu bạn biết kích thước danh sách:

def SplitList(mylist, chunk_size):
    return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]

Nếu bạn không (một trình vòng lặp):

def IterChunks(sequence, chunk_size):
    res = []
    for item in sequence:
        res.append(item)
        if len(res) >= chunk_size:
            yield res
            res = []
    if res:
        yield res  # yield the last, incomplete, portion

Trong trường hợp thứ hai, nó có thể được chia sẻ lại theo cách đẹp hơn nếu bạn có thể chắc chắn rằng chuỗi luôn chứa toàn bộ số khối có kích thước nhất định (nghĩa là không có đoạn cuối không hoàn chỉnh).


Tôi buồn điều này bị chôn vùi quá xa. IterChunks hoạt động cho tất cả mọi thứ và là giải pháp chung và không có sự cảnh báo nào mà tôi biết.
Jason Dunkelberger 7/8/2015

18

Các Toolz thư viện có partitionchức năng cho việc này:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]

Điều này có vẻ như đơn giản nhất trong tất cả các đề xuất. Tôi chỉ tự hỏi nếu nó thực sự có thể đúng là người ta phải sử dụng một thư viện bên thứ ba để có được một chức năng phân vùng như vậy. Tôi đã mong đợi một cái gì đó tương đương với chức năng phân vùng đó tồn tại dưới dạng ngôn ngữ dựng sẵn.
kasperd

1
bạn có thể làm một phân vùng với itertools. nhưng tôi thích thư viện toolz. nó là một thư viện lấy cảm hứng từ clojure để làm việc trên các bộ sưu tập theo phong cách chức năng. bạn không có được sự bất biến nhưng bạn có được một vốn từ vựng nhỏ để làm việc trên các bộ sưu tập đơn giản. Thêm vào đó, cytoolz được viết bằng cython và được tăng hiệu suất tốt. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Intellinging-CyToolz
zach 30/03/2015

Liên kết từ nhận xét của zach hoạt động nếu bạn sử dụng dấu gạch chéo: matthewrocklin.com/blog/work/2014/05/01/Int Giới thiệu
mit

17

Nếu bạn có kích thước chunk là 3 chẳng hạn, bạn có thể làm:

zip(*[iterable[i::3] for i in range(3)]) 

nguồn: http://code.activestate.com/recipes/303060-group-a-list-into- resultential-n-tuples/

Tôi sẽ sử dụng điều này khi kích thước khối của tôi là số cố định tôi có thể nhập, ví dụ '3' và sẽ không bao giờ thay đổi.


11
Điều này không hoạt động nếu len (lặp lại)% 3! = 0. Nhóm số (ngắn) cuối cùng sẽ không được trả về.
sherbang

16

Tôi thích phiên bản của tài liệu Python được đề xuất bởi tzot và JFSebastian, nhưng nó có hai nhược điểm:

  • nó không rõ ràng lắm
  • Tôi thường không muốn một giá trị điền vào đoạn cuối

Tôi đang sử dụng cái này rất nhiều trong mã của mình:

from itertools import islice

def chunks(n, iterable):
    iterable = iter(iterable)
    while True:
        yield tuple(islice(iterable, n)) or iterable.next()

CẬP NHẬT: Một phiên bản chunk lười biếng:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))

Điều kiện phá vỡ cho while Truevòng lặp là gì?
wjandrea

@wjandrea: Được StopIterationnâng lên khi tupletrống và iterable.next()được thực thi. Không hoạt động đúng bằng Python hiện đại tuy nhiên, nơi thoát một máy phát điện nên được thực hiện với return, không nâng cao StopIteration. Một try/except StopIteration: returnvòng quanh toàn bộ vòng lặp (và thay đổi iterable.next()thành next(iterable)compat phiên bản chéo) khắc phục điều này với ít nhất chi phí tối thiểu.
ShadowRanger

15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Trong đó AA là mảng, SS là kích thước khối. Ví dụ:

>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3

2
nó là tốt nhất và đơn giản
F.Tamy

2
ngắn gọn và đơn giản đơn giản hơn sự phức tạp.
dkrynicki

15

Tôi đã tò mò về hiệu suất của các phương pháp khác nhau và đây là:

Đã thử nghiệm trên Python 3.5.1

import time
batch_size = 7
arr_len = 298937

#---------slice-------------

print("\r\nslice")
start = time.time()
arr = [i for i in range(0, arr_len)]
while True:
    if not arr:
        break

    tmp = arr[0:batch_size]
    arr = arr[batch_size:-1]
print(time.time() - start)

#-----------index-----------

print("\r\nindex")
arr = [i for i in range(0, arr_len)]
start = time.time()
for i in range(0, round(len(arr) / batch_size + 1)):
    tmp = arr[batch_size * i : batch_size * (i + 1)]
print(time.time() - start)

#----------batches 1------------

def batch(iterable, n=1):
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

print("\r\nbatches 1")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#----------batches 2------------

from itertools import islice, chain

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


print("\r\nbatches 2")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#---------chunks-------------
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]
print("\r\nchunks")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in chunks(arr, batch_size):
    tmp = x
print(time.time() - start)

#-----------grouper-----------

from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(iterable, n, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

arr = [i for i in range(0, arr_len)]
print("\r\ngrouper")
start = time.time()
for x in grouper(arr, batch_size):
    tmp = x
print(time.time() - start)

Các kết quả:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844

3
điểm chuẩn bằng timethư viện không phải là một ý tưởng tuyệt vời khi chúng tôi có timeitmô-đun
Azat Ibrakov

13

mã:

def split_list(the_list, chunk_size):
    result_list = []
    while the_list:
        result_list.append(the_list[:chunk_size])
        the_list = the_list[chunk_size:]
    return result_list

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

print split_list(a_list, 3)

kết quả:

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

12

Bạn cũng có thể sử dụng get_chunkschức năng của utilspiethư viện như:

>>> from utilspie import iterutils
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(iterutils.get_chunks(a, 5))
[[1, 2, 3, 4, 5], [6, 7, 8, 9]]

Bạn có thể cài đặt utilspiequa pip:

sudo pip install utilspie

Tuyên bố miễn trừ trách nhiệm: Tôi là người tạo ra thư viện producspie .


11

Tại thời điểm này, tôi nghĩ rằng chúng ta cần một máy phát đệ quy , chỉ trong trường hợp ...

Trong trăn 2:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

Trong trăn 3:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    yield from chunks(li[n:], n)

Ngoài ra, trong trường hợp cuộc xâm lược ngoài hành tinh khổng lồ, một máy phát đệ quy được trang trí có thể trở nên tiện dụng:

def dec(gen):
    def new_gen(li, n):
        for e in gen(li, n):
            if e == []:
                return
            yield e
    return new_gen

@dec
def chunks(li, n):
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

9

Với Biểu thức chuyển nhượng trong Python 3.8, nó trở nên khá hay:

import itertools

def batch(iterable, size):
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item

Điều này hoạt động trên một vòng lặp tùy ý, không chỉ là một danh sách.

>>> import pprint
>>> pprint.pprint(list(batch(range(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

1
Bây giờ đây là một câu trả lời mới xứng đáng cho câu hỏi này. Tôi thực sự khá thích điều này. Tôi hoài nghi về các biểu thức chuyển nhượng, nhưng khi chúng hoạt động thì chúng hoạt động.
juanpa.arrivillaga

7

heh, phiên bản một dòng

In [48]: chunk = lambda ulist, step:  map(lambda i: ulist[i:i+step],  xrange(0, len(ulist), step))

In [49]: chunk(range(1,100), 10)
Out[49]: 
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

36
Vui lòng sử dụng "def chunk" thay vì "chunk = lambda". Nó hoạt động như nhau. Một đường thẳng. Tính năng tương tự. NHIỀU dễ dàng hơn để n00bz đọc và hiểu.
S.Lott

4
@ S.Lott: không phải nếu n00bz đến từ sơ đồ: P đây không phải là vấn đề thực sự. thậm chí còn có một từ khóa để google! Những tính năng nào khác cho thấy chúng tôi tránh vì lợi ích của n00bz? tôi đoán năng suất không bắt buộc / giống như c đủ để thân thiện với n00b.
Janus Troelsen

16
Đối tượng hàm kết quả từ def chunkthay vì chunk=lambdacó .__ tên _ thuộc tính 'chunk' thay vì '<lambda>'. Tên cụ thể hữu ích hơn trong tracebacks.
Terry Jan Reedy

1
@ Alfe: Tôi không chắc liệu có thể được gọi là một sự khác biệt về ngữ nghĩa chính hay không, nhưng liệu có một cái tên hữu ích trong một dấu vết thay vì <lamba>hay không, ít nhất là, một sự khác biệt đáng chú ý.
martineau

1
Sau khi thử nghiệm một loạt chúng cho hiệu suất, NÀY thật tuyệt!
Nắng Patel

7
def split_seq(seq, num_pieces):
    start = 0
    for i in xrange(num_pieces):
        stop = start + len(seq[i::num_pieces])
        yield seq[start:stop]
        start = stop

sử dụng:

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

for seq in split_seq(seq, 3):
    print seq

7

Một phiên bản rõ ràng hơn.

def chunkList(initialList, chunkSize):
    """
    This function chunks a list into sub lists 
    that have a length equals to chunkSize.

    Example:
    lst = [3, 4, 9, 7, 1, 1, 2, 3]
    print(chunkList(lst, 3)) 
    returns
    [[3, 4, 9], [7, 1, 1], [2, 3]]
    """
    finalList = []
    for i in range(0, len(initialList), chunkSize):
        finalList.append(initialList[i:i+chunkSize])
    return finalList

(2016 ngày 12 tháng 9) Câu trả lời này là ngôn ngữ độc lập nhất và dễ đọc nhất.
D Adams

7

Không gọi len () tốt cho danh sách lớn:

def splitter(l, n):
    i = 0
    chunk = l[:n]
    while chunk:
        yield chunk
        i += n
        chunk = l[i:i+n]

Và đây là cho iterables:

def isplitter(l, n):
    l = iter(l)
    chunk = list(islice(l, n))
    while chunk:
        yield chunk
        chunk = list(islice(l, n))

Các hương vị chức năng ở trên:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(l))))

HOẶC LÀ:

def chunks_gen_sentinel(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return iter(imap(tuple, continuous_slices).next,())

HOẶC LÀ:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))

16
Không có lý do để tránh len()trong danh sách lớn; đó là một hoạt động liên tục.
Thomas Wouters

7

Dưới đây là danh sách các phương pháp bổ sung:

Được

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Thư viện chuẩn

list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

d = {}
for i, x in enumerate(iterable):
    d.setdefault(i//n, []).append(x)

list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
    dd[i//n].append(x)

list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

more_itertools+

list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]

list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

Người giới thiệu

+ Thư viện của bên thứ ba thực hiện các công thức nấu ăn itertools và hơn thế nữa.> pip install more_itertools


6

Xem tài liệu tham khảo này

>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>> 

Python3


3
Đẹp, nhưng giảm các phần tử ở cuối nếu kích thước không khớp với toàn bộ số khối, ví dụ: zip(*[iter(range(7))]*3)chỉ trả về [(0, 1, 2), (3, 4, 5)]và quên 6từ đầu vào.
Alfe

6

Vì mọi người ở đây đều nói về các vòng lặp. boltonscó phương pháp hoàn hảo cho điều đó, được gọi là iterutils.chunked_iter.

from boltons import iterutils

list(iterutils.chunked_iter(list(range(50)), 11))

Đầu ra:

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

Nhưng nếu bạn không muốn thương xót bộ nhớ, bạn có thể sử dụng cách cũ và lưu trữ đầy đủ listở nơi đầu tiên với iterutils.chunked.


Và cái này thực sự hoạt động bất kể thứ tự nào nhìn vào các subiterators !!
Peter Gerdes 19/12/17

6

Thêm một giải pháp

def make_chunks(data, chunk_size): 
    while data:
        chunk, data = data[:chunk_size], data[chunk_size:]
        yield chunk

>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
...     print chunk
... 
[1, 2]
[3, 4]
[5, 6]
[7]
>>> 

5
def chunks(iterable,n):
    """assumes n is an integer>0
    """
    iterable=iter(iterable)
    while True:
        result=[]
        for i in range(n):
            try:
                a=next(iterable)
            except StopIteration:
                break
            else:
                result.append(a)
        if result:
            yield result
        else:
            break

g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'

1
Mặc dù điều này có thể trông không ngắn hoặc đẹp như nhiều câu trả lời dựa trên itertools, nhưng câu trả lời này thực sự hoạt động nếu bạn muốn in ra danh sách phụ thứ hai trước khi truy cập vào danh sách đầu tiên, tức là bạn có thể đặt i0 = next (g2); i1 = tiếp theo (g2); và sử dụng i1 trước khi sử dụng i0 và nó không bị hỏng !!
Peter Gerdes

5

Cân nhắc sử dụng matplotlib.cbook các mảnh

ví dụ:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s

Hình như bạn vô tình tạo hai tài khoản. Bạn có thể liên hệ với nhóm để hợp nhất chúng, điều này sẽ cho phép bạn lấy lại các đặc quyền chỉnh sửa trực tiếp trên các đóng góp của mình.
Georgy
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.