如何将任意长度的列表拆分为大小相等的块?
请参阅如果数据结果将直接用于循环,并且不需要存储,则如何以块形式遍历列表。
对于字符串输入的同一问题,请参见每n个字符拆分字符串?。相同的技术通常适用,但也有一些变化。
如何将任意长度的列表拆分为大小相等的块?
请参阅如果数据结果将直接用于循环,并且不需要存储,则如何以块形式遍历列表。
对于字符串输入的同一问题,请参见每n个字符拆分字符串?。相同的技术通常适用,但也有一些变化。
当前回答
我创建了这两个漂亮的一行程序,它们既高效又懒惰,输入和输出都是可迭代的,而且它们不依赖于任何模块:
首先,一行是完全懒惰的,这意味着它返回迭代器生成迭代器(即,生成的每个块都是迭代器对块的元素进行迭代),如果块非常大或元素一个接一个地缓慢生成,并且在生成时应立即可用,则此版本适用于这种情况:
在线试用!
chunk_iters = lambda it, n: ((e for i, g in enumerate(((f,), cit)) for j, e in zip(range((1, n - 1)[i]), g)) for cit in (iter(it),) for f in cit)
第二行返回生成列表的迭代器。一旦整个块的元素通过输入迭代器变得可用,或者到达最后一个块的最后一个元素,就会生成每个列表。如果输入元素快速生成或立即全部可用,则应使用此版本。应该使用其他明智的第一个更懒惰的一行代码版本。
在线试用!
chunk_lists = lambda it, n: (l for l in ([],) for i, g in enumerate((it, ((),))) for e in g for l in (l[:len(l) % n] + [e][:1 - i],) if (len(l) % n == 0) != i)
此外,我还提供了第一个chunk_iter的多行版本一行,它返回迭代器生成另一个迭代器(遍历每个chunk的元素):
在线试用!
def chunk_iters(it, n):
cit = iter(it)
def one_chunk(f):
yield f
for i, e in zip(range(n - 1), cit):
yield e
for f in cit:
yield one_chunk(f)
其他回答
我知道这有点过时,但还没有人提到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])]
简单而优雅
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)
如何将列表分割成大小均匀的块?
对我来说,“大小均匀的块”意味着它们都是相同的长度,或者除非有这种选择,长度上的差异最小。例如,21个项目的5个篮子可能具有以下结果:
>>> import statistics
>>> statistics.variance([5,5,5,5,1])
3.2
>>> statistics.variance([5,4,4,4,4])
0.19999999999999998
更倾向于后一种结果的一个实际原因是:如果你使用这些函数来分配工作,你已经内置了一个可能比其他人完成得好的前景,因此当其他人继续努力工作时,它会无所事事。
此处对其他答案的批评
当我最初写这个答案时,没有一个其他答案是大小均匀的块——它们都会在最后留下一个小块,所以它们没有很好地平衡,并且长度的差异超过了必要的范围。
例如,当前顶部答案以:
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]
其他如列表(grouper(3,range(7))和块(range(7,3))都返回:[(0,1,2),(3,4,5),(6,None,None)]。“无”只是填充,在我看来相当不雅。他们并没有将可迭代项平均分块。
为什么我们不能更好地划分这些呢?
循环解决方案
一个使用itertools.cycle的高级平衡解决方案,这就是我今天可能采用的方法。设置如下:
from itertools import cycle
items = range(10, 75)
number_of_baskets = 10
现在我们需要我们的列表来填充元素:
baskets = [[] for _ in range(number_of_baskets)]
最后,我们将要分配的元素与一个篮子循环压缩在一起,直到元素用完,从语义上讲,这正是我们想要的:
for element, basket in zip(items, cycle(baskets)):
basket.append(element)
结果如下:
>>> from pprint import pprint
>>> pprint(baskets)
[[10, 20, 30, 40, 50, 60, 70],
[11, 21, 31, 41, 51, 61, 71],
[12, 22, 32, 42, 52, 62, 72],
[13, 23, 33, 43, 53, 63, 73],
[14, 24, 34, 44, 54, 64, 74],
[15, 25, 35, 45, 55, 65],
[16, 26, 36, 46, 56, 66],
[17, 27, 37, 47, 57, 67],
[18, 28, 38, 48, 58, 68],
[19, 29, 39, 49, 59, 69]]
为了使这个解决方案产品化,我们编写了一个函数,并提供了类型注释:
from itertools import cycle
from typing import List, Any
def cycle_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
baskets = [[] for _ in range(min(maxbaskets, len(items)))]
for item, basket in zip(items, cycle(baskets)):
basket.append(item)
return baskets
在上面,我们列出了物品清单,以及篮子的最大数量。我们创建一个空列表列表,在其中以循环方式追加每个元素。
片
另一个优雅的解决方案是使用切片,特别是不太常用的切片步骤参数。即。:
start = 0
stop = None
step = number_of_baskets
first_basket = items[start:stop:step]
这一点特别优雅,因为切片不关心数据的长度-结果,我们的第一个篮子,只要它需要的长度就可以了。我们只需要增加每个篮子的起点。
事实上,这可能是一行代码,但为了可读性和避免代码过长,我们将使用多行代码:
from typing import List, Any
def slice_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
n_baskets = min(maxbaskets, len(items))
return [items[i::n_baskets] for i in range(n_baskets)]
来自itertools模块的islice将提供一种懒惰的迭代方法,就像问题中最初要求的那样。
我不认为大多数用例会受益匪浅,因为原始数据已经在列表中完全具体化,但对于大型数据集,它可以节省近一半的内存使用。
from itertools import islice
from typing import List, Any, Generator
def yield_islice_baskets(items: List[Any], maxbaskets: int) -> Generator[List[Any], None, None]:
n_baskets = min(maxbaskets, len(items))
for i in range(n_baskets):
yield islice(items, i, None, n_baskets)
查看结果:
from pprint import pprint
items = list(range(10, 75))
pprint(cycle_baskets(items, 10))
pprint(slice_baskets(items, 10))
pprint([list(s) for s in yield_islice_baskets(items, 10)])
更新了以前的解决方案
这是另一个平衡的解决方案,改编自我过去在生产中使用的函数,它使用模运算符:
def baskets_from(items, maxbaskets=25):
baskets = [[] for _ in range(maxbaskets)]
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 range(baskets):
yield [items[y_i] for y_i in range(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 range(baskets):
length = ceiling if x_i < stepdown else floor
yield [items.next() for _ in range(length)]
输出
要测试它们:
print(baskets_from(range(6), 8))
print(list(iter_baskets_from(range(6), 8)))
print(list(iter_baskets_contiguous(range(6), 8)))
print(baskets_from(range(22), 8))
print(list(iter_baskets_from(range(22), 8)))
print(list(iter_baskets_contiguous(range(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(range(26), 5))
print(list(iter_baskets_from(range(26), 5)))
print(list(iter_baskets_contiguous(range(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]]
注意,连续生成器以与其他两个相同的长度模式提供块,但这些项都是有序的,并且它们被均匀地划分,就像可以划分离散元素列表一样。
我专门为此写了一个小图书馆,这里有。库的分块函数特别有效,因为它是作为生成器实现的,因此在某些情况下可以节省大量内存。它也不依赖切片表示法,因此可以使用任意迭代器。
import iterlib
print list(iterlib.chunked(xrange(1, 1000), 10))
# prints [(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), (11, 12, 13, 14, 15, 16, 17, 18, 19, 20), ...]
这是一个生成大小均匀的块的生成器:
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)]