如何将列表分成大小均匀的块?


2263

我有一个任意长度的列表,我需要将其分成相等大小的块并对其进行操作。有一些明显的方法可以做到这一点,例如保留一个计数器和两个列表,当第二个列表填满时,将其添加到第一个列表中,并为第二轮数据清空第二个列表,但这可能会非常昂贵。

我想知道是否有人对任何长度的列表都有很好的解决方案,例如使用生成器。

我一直在寻找有用的东西,itertools但找不到任何明显有用的东西。可能已经错过了。

相关问题:遍历大块列表的最“ pythonic”方法是什么?


1
发布新答案之前,请考虑该问题已经有60多个答案。请确保您的答案提供的信息不属于现有答案。
janniks

对于想要避免任意小的最终块的用户,请查看将列表分成大约相等长度的N个部分
wim

Answers:


3144

这是一个生成所需块的生成器:

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

如果您使用的是Python 2,则应使用xrange()而不是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]

同样,您可以简单地使用列表理解而不是编写函数,尽管将这样的操作封装在命名函数中是个好主意,这样您的代码更易于理解。Python 3:

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

Python 2版本:

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

71
如果我们不知道清单的长度怎么办?在itertools.repeat([[1,2,3])上尝试一下,例如
jespern

47
这是对该问题的有趣扩展,但原始问题显然询问了有关对列表进行操作的问题。
Ned Batchelder

32
此功能需要在该死的标准库中
dgan

6
@Calimo:您有什么建议?我递给你一份包含47个要素的清单。您想如何将其拆分为“均匀大小的块”?OP接受了答案,因此显然可以接受最后一个不同大小的块。也许英语短语不准确?
Ned Batchelder

7
请不要命名您的变量l,它看起来完全像1并且令人困惑。人们正在复制您的代码,并认为可以。
Yasen,

555

如果您想要超级简单的东西:

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

在Python 2.x中使用xrange()代替range()


6
或者(如果我们要对该特定函数进行不同的表示),则可以通过以下方式定义一个lambda函数:lambda x,y:[[[[i:i + y] for range(0,len(x),y​​)中的i) ]。我喜欢这种列表理解方法!
JP

4
返回后必须有[,而不是(
alwbtc

2
“超级简单”意味着不必调试无限循环-对于 max()
鲍勃·斯坦

这个解决方案没有什么简单的
-MIT

1
@Nhoj_Gonk糟糕,这不是一个无限循环,但是不具有max()的情况下,chunks(L,0)会引发ValueError。相反,max()会将小于1的任何内容转换为1。
鲍勃·斯坦因

294

直接来自(旧的)Python文档(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)

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)

我猜想Guido的时间机器可以工作了,可以工作了,可以工作了,可以再次工作。

这些解决方案之所以有效,是因为[iter(iterable)]*n(或早期版本中的等效项)创建了一个迭代器,n并在列表中重复了几次。izip_longest然后有效地执行“每个”迭代器的循环;因为这是相同的迭代器,所以每次此类调用都会对其进行高级处理,从而使每个此类zip-roundrobin生成一个元组n项。


@ninjagecko:list(grouper(3, range(10)))返回[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)],所有元组的长度为3。请详细说明您的评论,因为我听不懂。在“期望事物是3的倍数”中,您如何称呼事物以及如何将其定义为3的倍数?先感谢您。
tzot 2011年

14
之所以提出这一建议,是因为它适用于生成器(无len),并且使用通常更快的itertools模块。
Michael Dillon 2012年

88
itertools与简单而幼稚的纯python实现相比,花哨的功能性方法会产生一些难以理解的污泥的经典示例
wim 2013年

15
@wim鉴于此答案开始于Python文档的摘要,我建议您在bugs.python.org上打开一个问题。
tzot

1
@pedrosaurio如果l==[1, 2, 3]f(*l)等同于f(1, 2, 3)。请参阅该问题官方文档
tzot

224

我知道这有点陈旧,但没有人提到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
这使您可以设置块的总数,而不是每个块的元素数。
FizxMike 2015年

6
你可以自己做数学。如果您有10个元素,则可以将它们分为2个,5个元素块或五个2个元素块
Moj 2015年

24
+1这是我最喜欢的解决方案,因为它将阵列分成大小均匀的阵列,而其他解决方案则没有(在我研究的所有其他解决方案中,最后一个阵列可能很小)。
MiniQuark

@MiniQuark,但是当块数不是原始数组大小的因素时,该怎么办?
鲍德里克

1
@Baldrickk如果将N个元素拆分为K个块,则前N%K个块将具有N // K + 1个元素,其余的将具有N // K个元素。例如,如果将包含108个元素的数组拆分为5个块,则前108%5 = 3个块将包含108 // 5 + 1 = 22个元素,其余块将具有108 // 5 = 21元素。
MiniQuark

147

令我惊讶的是,没有人想到使用iter二元形式

from itertools import islice

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

演示:

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

这可以与任何迭代一起工作,并产生延迟输出。它返回元组而不是迭代器,但是我认为它仍然具有一定的优雅。它也不会填充;如果您想进行填充,则只需对上述内容进行简单的修改即可:

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)

演示:

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

izip_longest基于解决方案的解决方案一样,以上内容总是可以解决的。据我所知,没有可选的填充函数的单行或两行itertools配方。通过结合以上两种方法,这一方法非常接近:

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

演示:

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

我认为这是最短的分块器,建议提供可选的填充。

饰演Tomasz Gandor 观察到的,如果两个填充分块器遇到一长串填充值,它们将意外停止。这是一个可以合理解决该问题的最终变体:

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

演示:

>>> 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
太好了,您的简单版本是我的最爱。其他人也想出了基本islice(it, size)表达式,并将其嵌入到循环结构中(就像我所做的那样)。只有您想到了iter()(我完全不知道)的两个参数的版本,这使其变得非常优雅(并且可能是最有效的)。我不知道iter给定前哨后,第一个参数更改为0参数函数。您返回块的(锅无限)迭代器,可以使用(锅无限)迭代器作为输入,len()没有数组切片,也没有数组切片。太棒了!
ThomasH 16/09/15

1
这就是为什么我通读答案而不是只扫描前几对的原因。在我的情况下,可选填充是必需的,我也了解了iter的两个参数形式。
科尔(Kerr)

我对此表示赞同,但仍然-不要夸大其词!首先,lambda可能是不好的(it迭代器的关闭速度很慢。第二,也是最重要的-如果padval您的可迭代对象中确实存在一部分,则将过早结束,应进行处理。)
Tomasz Gandor

@TomaszGandor,我明白你的第一点!尽管我的理解是lambda并不比普通函数慢,但您当然是对的,因为函数调用和闭包查找会降低它的速度。例如,我不知道与izip_longest方法相比,相对性能的影响是什么-我怀疑这可能是一个复杂的权衡。但是...不是padval每个提供padval参数的答案都存在这个问题吗?
senderle '18

1
@TomaszGandor,很公平!但是创建一个可以解决这个问题的版本并不难。(另外,请注意,()用作哨兵的第一个版本确实可以正常工作。这是因为在空的情况下会tuple(islice(it, size))产生收益。)()it
senderle

93

这是处理任意可迭代对象的生成器:

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

例:

>>> 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)等于izip_longest(iter)
Thomas Ahle 2012年

1
@TomaszWysocki您能解释一下*迭代器元组前面的吗?可能在您的答案文本中,但我注意到*以前在Python 中使用过这种方式。谢谢!
theJollySin

1
@theJollySin在这种情况下,它称为splat运算符。此处说明了其用法-stackoverflow.com/questions/5917522/unzipping-and-the-operator
rlms

2
关闭,但最后一块没有元素可填充。这可能是缺陷,也可能不是缺陷。真的很酷的模式。

48

简单而优雅

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

或者,如果您喜欢:

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

18
请勿像阿拉伯数字一样复制变量。在某些字体中,1l没有区别。由于是0O。有时甚至I1
Alfe 2013年

14
@Alfe不良字体。人们不应该使用此类字体。不用于编程,不用于任何东西
杰里·B

17
Lambda被用作未命名的函数。这样使用它们是没有意义的。另外,由于发生错误时,回溯将以“ <lambda>”而不是“块”形式报告,因此这使调试更加困难。祝您好运,如果您有一堆这样的问题,请尝试:)
Chris Koston 2013年

1
在xrange中应该是0,而不是1print [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta 2013年

注意:对于Python 3用户,请使用range
克里斯汀·迪恩

40

批判其他答案在这里:

这些答案都不是均匀大小的块,它们都在末尾留下欠缺的块,因此它们并不完全平衡。如果您使用这些功能来分配工作,那么您就建立了一个前景可能比其他事情早完成的前景,因此在其他人继续努力的同时,它什么也没做。

例如,当前的最佳答案以:

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

我只是讨厌最后那个矮子!

其他人,例如list(grouper(3, xrange(7)))和,chunk(xrange(7), 3)都返回:[(0, 1, 2), (3, 4, 5), (6, None, None)]。的None只是填充,在我看来相当不雅。他们没有将可迭代对象均匀地分块。

为什么我们不能更好地划分这些?

我的解决方案

这是一个平衡的解决方案,它是根据我在生产环境中使用过的函数改编而成的(Python 3中的Note替换xrangerange):

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) 

我创建了一个生成器,如果将其放入列表中,它的功能也相同:

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

最后,由于我看到上述所有函数均按连续顺序返回元素(如给出的那样):

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

输出量

要测试它们:

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

打印出:

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

请注意,连续生成器以与其他两个相同的长度模式提供块,但是所有项都是有序的,并且它们被均匀地划分为一个可以划分一列离散元素的形式。


您说以上都不提供均匀大小的块。但是,这一样
senderle 2014年

1
@senderle,第一个list(grouper(3, xrange(7)))和第二个chunk(xrange(7), 3)都返回:[(0, 1, 2), (3, 4, 5), (6, None, None)]。的None只是填充,在我看来相当不雅。他们没有将可迭代对象均匀地分块。感谢您的投票!
亚伦·霍尔

4
您提出了一个问题(没有明确地做,所以我现在就在这里做)是是否需要更多相等大小的块(如果不可能的话,最后一个块除外),或者是否更需要平衡的(尽可能好的)结果。您认为平衡的解决方案是首选。如果您所编程的内容接近真实世界(例如,用于模拟纸牌游戏的纸牌交易算法),则可能会如此。在其他情况下(例如用单词填充行),人们宁愿保持行尽可能完整。所以我真的不能比另一个更喜欢。它们仅用于不同的用例。
Alfe

@ ChristopherBarrington-Leigh好的一点,对于DataFrame,您应该使用切片,因为我相信DataFrame对象通常不会在切片时复制,例如import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall

1
@AaronHall糟糕。我删除了评论,因为我对评论提出了第二个猜测,但是您很快就得出了结论。谢谢!实际上,我声称它不适用于数据帧是正确的。如果items是一个数据框,则仅使用yield items [range(x_i,item_count,baskets)]作为最后一行。我提供了一个单独的答案(另一个),您可以在其中指定所需的(最小)小组人数。
CPBL 2014年

38

我在其中看到了最棒的Python式答案 这个问题重复部分中,:

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

您可以为任何n个创建n个元组。如果为a = range(1, 15),则结果将为:

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

如果列表平均分配,则可以替换zip_longestzip,否则三元组(13, 14, None)将丢失。上面使用了Python 3。对于Python 2,请使用izip_longest


如果您的列表和块很短,那很好,如何调整列表以将其分成1000个块呢?您不会使用邮政编码(i,i,i,i,i,i,i,i,i,i ..... i = 1000)
汤姆·史密斯

9
zip(i, i, i, ... i)带有zip()参数的“ chunk_size”参数可以写成:这zip(*[i]*chunk_size)是否是一个好主意,当然值得商bat。
Wilson F

1
这样做的缺点是,如果您没有平均分配,您将删除元素,因为zip在最短的可迭代位置停止-&izip_longest会添加默认元素。
亚伦·霍尔

zip_longest应该使用,例如:stackoverflow.com/a/434411/1959808
Ioannis Filippidis

与答案range(1, 15)已经缺失的元素,因为在14元range(1, 15),而不是15
扬FILIPPIDIS

35

如果您知道列表大小:

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

如果不这样做(一个迭代器):

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

在后一种情况下,如果可以确定序列始终包含给定大小的所有块(即不存在不完整的最后一个块),则可以用更漂亮的方式来重新措词。


我很伤心,它被埋得如此之深。IterChunks适用于所有内容,是通用解决方案,我没有任何警告。
杰森·邓克伯格2015年

18

图尔茨库具有partition此功能:

from toolz.itertoolz.core import partition

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

这看起来是所有建议中最简单的。我只是想知道是否真的必须使用第三方库来获得这种分区功能。我希望与该分区功能等效的东西可以作为内置语言存在。
卡巴斯德,2015年

1
您可以使用itertools进行分区。但是我喜欢toolz库。它是一个受clojure启发的库,用于以功能性样式处理集合。您不会一成不变,但会获得一些用于处理简单集合的词汇。另外,cytoolz是用cython编写的,因此性能得到了很好的提升。github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
扎克-

如果您省略斜杠,则zach评论的链接将起作用:matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
mit


16

我非常喜欢tzot和JFSebastian提出的Python文档版本,但是它有两个缺点:

  • 这不是很明确
  • 我通常不希望在最后一块填充值

我在代码中经常使用此代码:

from itertools import islice

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

更新:惰性块版本:

from itertools import chain, islice

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

while True循环的中断条件是什么?
wjandrea

@wjandrea:StopIterationtuple为空iterable.next()并被执行时引发。但是在现代Python中无法正常工作,在该情况下退出生成器应使用return,而不是升高StopIteration。一个try/except StopIteration: return围绕着整个环(和不断变化的iterable.next(),以next(iterable)跨版COMPAT)以最小的开销至少修复此。
ShadowRanger

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

其中AA是数组,SS是块大小。例如:

>>> 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
这是最好的和简单的。
F.Tamy,

2
简短。简单而不是复杂。
dkrynicki

15

我很好奇不同方法的性能,这里是:

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

结果:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844

3
time当我们有timeit模块时,使用库进行基准测试并不是一个好主意
Azat Ibrakov

13

码:

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)

结果:

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

12

您还可以get_chunksutilspie库函数用作:

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

您可以utilspie通过pip 安装:

sudo pip install utilspie

免责声明:我是utilspie library 的创建者


11

在这一点上,我认为我们需要一个递归生成器,以防万一...

在python 2:

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

在python 3:

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

同样,在外星人大规模入侵的情况下,经过修饰的递归生成器可能会派上用场:

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

使用Python 3.8中的赋值表达式,它变得非常不错:

import itertools

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

这适用于任意迭代,而不仅仅是列表。

>>> 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
现在,这是一个值得回答的新问题。我其实很喜欢这样。我对赋值表达式表示怀疑,但是当它们起作用时,它们就会起作用。
juanpa.arrivillaga

7

呵呵,单行版

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
请使用“ def块”而不是“ chunk = lambda”。它的工作原理相同。一条线。相同的功能。使n00bz更容易阅读和理解。
S.Lott

4
@ S.Lott:如果n00bz来自scheme:P,那不是真正的问题。甚至还有一个谷歌关键字!为了n00bz,我们还要避免哪些其他功能?我想产量不是命令式的/ c足以使n00b友好。
Janus Troelsen,2012年

16
def chunk而不是产生的函数对象chunk=lambda具有.__ name__属性'chunk'而不是'<lambda>'。该特定名称在回溯中更有用。
特里·扬·里迪

1
@Alfe:我不确定是否可以将其称为主要的语义差异,但是<lamba>至少在回溯中是否存在有用的名称而不是显着的差异。
martineau 2015年

1
在测试了其中的一组性能之后,这很棒!
Sunny 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

用法:

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

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

7

另一个更明确的版本。

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年9月12日)此答案是最独立于语言且最容易阅读的答案。
D Adams

7

在不调用len()的情况下,该方法非常适合大型列表:

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

这是针对可迭代对象的:

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

以上功能的味道:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(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,())

要么:

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
没有理由避免len()在大名单上;这是一个恒定时间的操作。
Thomas Wouters

7

以下是其他方法的列表:

给定

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

标准图书馆

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

参考文献

+一个实现itertools配方等的第三方库。> pip install more_itertools


6

看到这个参考

>>> 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
很好,但是如果大小与整数块不匹配,则在最后删除元素,例如,zip(*[iter(range(7))]*3)仅返回[(0, 1, 2), (3, 4, 5)]6从输入中忘记了。
阿尔夫,2013年

6

由于这里的每个人都在谈论迭代器。boltons为此有一个完美的方法,称为iterutils.chunked_iter

from boltons import iterutils

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

输出:

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

但是,如果您不想对内存存留怜悯,可以使用old-way并通过将数据存储list在第一位iterutils.chunked


实际上,不管顺序如何,我们都可以正常工作!
彼得·格德斯

6

另一种解决方案

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
虽然这看起来可能不像基于Itertools的响应那么短,但是如果您想在访问第一个子列表之前先打印出第二个子列表,则可以实际使用它,例如,可以设置i0 = next(g2);i1 = next(g2); 并在使用i0之前先使用i1,它不会损坏!
彼得·格德斯

5

考虑使用matplotlib.cbook片段

例如:

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

您好像不小心创建了两个帐户。您可以与团队联系以合并他们,这将使您可以重新获得文稿的直接编辑权限。
乔治,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.