题 如何将列表拆分为大小均匀的块?


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

我想知道是否有人对任何长度的列表都有一个很好的解决方案,例如使用发电机。

我一直在寻找有用的东西 itertools 但我找不到任何明显有用的东西。但是可能会错过它。

相关问题: 在块中迭代列表的最“pythonic”方法是什么?


1580
2017-11-23 12:15


起源


这里是一个优化的解决方案(更友好的内存) stackoverflow.com/questions/7133179/python-yield-and-delete - Radim
FWIW,图书馆 more_itertools 提供一个 chunked 以有效的方式完成这项工作的功能。 - bgusach


答案:


这是一个产生你想要的块的生成器:

def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[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(l, n):
    """Yield successive n-sized chunks from l."""
    for i in xrange(0, len(l), n):
        yield l[i:i + n]

您也可以简单地使用列表理解而不是编写函数。 Python 3:

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

Python 2版本:

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

2116
2017-11-23 12:33



如果我们无法分辨清单的长度会怎样?在itertools.repeat([1,2,3])上试试这个,例如 - jespern
这是一个有趣的问题扩展,但最初的问题清楚地询问了如何在列表上进行操作。 - Ned Batchelder
2to3移植程序将所有xrange调用更改为range,因为在Python 3.0中,range的功能将等于xrange的功能(即它将返回迭代器)。所以我会避免使用范围而是使用xrange。 - Tomi Kyöstilä
@attz实际上 range 已从Python 3.0中删除 xrange 被重命名为 range。 - Kos
@zedr,“元组理解”实际上是一个“生成器表达”。元组理解会更像 tuple(l[i:i+n] for i in xrange(0, len(l), n))。 :-) - Ben Hoyt


如果你想要一些超级简单的事:

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

481
2017-11-17 20:17



或者(如果我们对这个特定函数进行不同的表示)你可以通过以下方式定义一个lambda函数:lambda x,y:[x [i:i + y] for i in range(0,len(x),y​​) ]。我喜欢这种列表理解方法! - J-P
返回后必须[,不是( - alwbtc
@alwbtc - 不,它是正确的它是一个发电机 - Mr_and_Mrs_D
“超级简单”意味着不必调试无限循环 - 为此感到荣幸 max()。 - Bob Stein


直接来自(旧)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)

目前的版本,由J.F.Sebastian建议:

#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 项目。


251
2017-11-23 15:48



它是 izip_longest(*[iter(iterable)]*n, fillvalue=fillvalue) 如今。 - jfs
赞成这一点,因为它适用于生成器(没有len)并使用通常更快的itertools模块。 - Michael Dillon
您可以将这一切组合成一个简短的单行: zip(*[iter(yourList)]*n) (要么 izip_longest 有fillvalue) - ninjagecko
花哨的典型例子 itertools 与简单而天真的纯python实现相比,功能方法产生了一些难以理解的污泥 - wim
@wim鉴于此答案始于Python文档的片段,我建议你打开一个问题 bugs.python.org 。 - tzot


我知道这有点旧,但我不知道为什么没人提到 numpy.array_split

lst = range(50)
In [26]: np.array_split(lst,5)
Out[26]: 
[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])]

94
2018-06-05 08:54



这允许您设置块的总数,而不是每个块的元素数。 - FizxMike
你可以自己做数学。如果你有10个元素,你可以将它们分为2,5个元素块或5个2元素块 - Moj
+1这是我最喜欢的解决方案,因为它将数组拆分为 匀 大小的数组,而其他解决方案没有(在我看到的所有其他解决方案中,最后一个数组可能是任意小的)。 - MiniQuark
我希望我在5年前找到这个。谢谢@Moj非常有用的功能。 - O.rka
@MiniQuark但是当块数不是原始数组大小的因素时,它会怎么做? - Baldrickk


这是一个适用于任意迭代的生成器:

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

79
2017-11-23 12:41





我很惊讶没人想过要用 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')]

我相信这是提供可选填充的最短时间段。


65
2018-02-26 15:02



很棒,你的简单版本是我的最爱。其他人也提出了基本的 islice(it, size) 表达式并将其嵌入循环结构中(就像我所做的那样)。只有你想到了两个版本的 iter() (我完全没有意识到),这使它超级优雅(可能最具性能效果)。我不知道第一个参数 iter 给定哨兵时,更改为0参数函数。你返回一个(pot。无限)块的迭代器,可以使用一个(pot。无限)迭代器作为输入,没有 len() 没有数组切片。真棒! - ThomasH
这就是为什么我读下答案而不是只扫描顶级情侣。在我的情况下,可选填充是一个要求,我也学习了iter的两个参数形式。 - Kerr


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

47
2018-06-26 19:10



map(None, iter) 等于 izip_longest(iter)。 - Thomas Ahle
@TomaszWysocki你能解释一下 * 在你面前的迭代元组?可能在你的答案文本中,但我已经注意到了 * 之前在Python中使用过这种方式。谢谢! - theJollySin
@theJollySin在这种情况下,它被称为splat运算符。它的用途在这里解释 - stackoverflow.com/questions/5917522/unzipping-and-the-operator。 - rlms
关闭,但最后一个块有无元素填写它。这可能是也可能不是缺陷。虽然真的很酷。


简约而优雅

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

或者如果您愿意:

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

39
2017-07-12 07:58



你不应该用阿拉伯数字相似的变量。在某些字体中, 1 和 l 无法区分。就好像 0 和 O。甚至有时甚至 I 和 1。 - Alfe
@Alfe有缺陷的字体。人们不应该使用这样的字体。不是为了编程,不是为了编程 什么。 - Jerry B
Lambdas旨在用作未命名的函数。没有必要像这样使用它们。此外,它使调试更加困难,因为如果出现错误,回溯将报告“in <lambda>”而不是“in chunk”。如果你有一大堆这些,我希望你找到问题的运气:) - Chris Koston
它应该是0而不是1里面的xrange print [l[x:x+10] for x in xrange(1, len(l), 10)] - scottydelta
注意: 对于Python 3用户使用 range。 - Christian Dean


我在一篇文章中看到了最棒的Python-ish答案 重复 这个问题:

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_longest 同 zip,否则是三胞胎 (13, 14, None) 会丢失的。上面使用了Python 3。对于Python 2,请使用 izip_longest


28
2018-03-12 12:36



如果你的列表和块很短,这很好,你怎么能适应这个将你的列表拆分为1000块呢?你不打算拉码(我,我,我,我,我,我,我,我,我,我......我= 1000) - Tom Smith
zip(i, i, i, ... i) zip()的“chunk_size”参数可以写成 zip(*[i]*chunk_size) 当然,这是否是一个好主意是值得商榷的。 - Wilson F
这样做的缺点是,如果你没有均匀分割,你将丢弃元素,因为zip停止在最短的可迭代 - &izip_longest将添加默认元素。 - Aaron Hall♦
zip_longest 应该使用,如: stackoverflow.com/a/434411/1959808 - Ioannis Filippidis
答案是 range(1, 15) 已经缺少元素,因为有14个元素 range(1, 15),而不是15。 - Ioannis Filippidis