如何将任意长度的列表拆分为大小相等的块?


请参阅如果数据结果将直接用于循环,并且不需要存储,则如何以块形式遍历列表。

对于字符串输入的同一问题,请参见每n个字符拆分字符串?。相同的技术通常适用,但也有一些变化。


当前回答

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

其他回答

一个简单的解决方案

OP已请求“相等大小的块”。我将“等尺寸”理解为“平衡”尺寸:如果尺寸不可能相等(例如,23/5),我们正在寻找尺寸大致相同的物品组。

这里的输入是:

项目列表:input_list(例如,23个数字的列表)要拆分这些项目的组数:n个组(例如5个)

输入:

input_list = list(range(23))
n_groups = 5

连续元素组:

approx_sizes = len(input_list)/n_groups 

groups_cont = [input_list[int(i*approx_sizes):int((i+1)*approx_sizes)] 
               for i in range(n_groups)]

“每N个”元素组:

groups_leap = [input_list[i::n_groups] 
               for i in range(n_groups)]

后果

print(len(input_list))

print('Contiguous elements lists:')
print(groups_cont)

print('Leap every "N" items lists:')
print(groups_leap)

将输出:23连续元素列表:[[0, 1, 2, 3], [4, 5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16, 17], [18, 19, 20, 21, 22]]跳过每“N”个项目列表:[[0, 5, 10, 15, 20], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18], [4, 9, 14, 19]]

您可以使用numpy的array_split函数,例如np.array_split(np.array(data),20),将其拆分为20个大小几乎相等的块。

要确保块的大小完全相等,请使用np.split。

下面我有一个解决方案确实有效,但比这个解决方案更重要的是对其他方法的一些评论。首先,一个好的解决方案不应该要求一个循环按顺序遍历子迭代器。如果我跑

g = paged_iter(list(range(50)), 11))
i0 = next(g)
i1 = next(g)
list(i1)
list(i0)

最后一个命令的适当输出是

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

not

 []

正如这里大多数基于itertools的解决方案所返回的那样。这不仅仅是关于按顺序访问迭代器的常见无聊限制。想象一个消费者试图清理输入不良的数据,该数据颠倒了5的块的适当顺序,即数据看起来像[B5,A5,D5,C5],应该像[A5,B5,C5,D5](其中A5只是五个元素,而不是子列表)。该使用者将查看分组函数的声明行为,并毫不犹豫地编写一个类似

i = 0
out = []
for it in paged_iter(data,5)
    if (i % 2 == 0):
         swapped = it
    else: 
         out += list(it)
         out += list(swapped)
    i = i + 1

如果您偷偷摸摸地假设子迭代器总是按顺序完全使用,那么这将产生神秘的错误结果。如果你想交错块中的元素,情况就更糟了。

其次,大量建议的解决方案隐含地依赖于迭代器具有确定性顺序的事实(例如,迭代器没有设置),尽管使用islice的一些解决方案可能还可以,但我对此感到担忧。

第三,itertools-grouper方法有效,但该方法依赖于zip_langest(或zip)函数的内部行为,而这些行为不是其发布行为的一部分。特别是,grouper函数只起作用,因为在zip_langest(i0…In)中,下一个函数总是按next(i0)、next(i 1)、……的顺序调用。。。在重新开始之前。当grouper传递同一迭代器对象的n个副本时,它依赖于此行为。

最后,虽然下面的解决方案可以得到改进,但如果您对上面的假设进行了批评,即子迭代器是按顺序访问的,并且在没有这个假设的情况下被完全阅读,则必须隐式(通过调用链)或显式(通过deques或其他数据结构)为每个子迭代程序存储元素。所以,不要浪费时间(就像我所做的那样),假设人们可以用一些巧妙的技巧来解决这个问题。

def paged_iter(iterat, n):
    itr = iter(iterat)
    deq = None
    try:
        while(True):
            deq = collections.deque(maxlen=n)
            for q in range(n):
                deq.append(next(itr))
            yield (i for i in deq)
    except StopIteration:
        yield (i for i in deq)

任何可迭代的通用分块器,使用户可以选择如何在结尾处处理部分分块。

在Python 3上测试。

分块.py

from enum import Enum

class PartialChunkOptions(Enum):
    INCLUDE = 0
    EXCLUDE = 1
    PAD = 2
    ERROR = 3

class PartialChunkException(Exception):
    pass

def chunker(iterable, n, on_partial=PartialChunkOptions.INCLUDE, pad=None):
    """
    A chunker yielding n-element lists from an iterable, with various options
    about what to do about a partial chunk at the end.

    on_partial=PartialChunkOptions.INCLUDE (the default):
                     include the partial chunk as a short (<n) element list

    on_partial=PartialChunkOptions.EXCLUDE
                     do not include the partial chunk

    on_partial=PartialChunkOptions.PAD
                     pad to an n-element list 
                     (also pass pad=<pad_value>, default None)

    on_partial=PartialChunkOptions.ERROR
                     raise a RuntimeError if a partial chunk is encountered
    """

    on_partial = PartialChunkOptions(on_partial)        

    iterator = iter(iterable)
    while True:
        vals = []
        for i in range(n):
            try:
                vals.append(next(iterator))
            except StopIteration:
                if vals:
                    if on_partial == PartialChunkOptions.INCLUDE:
                        yield vals
                    elif on_partial == PartialChunkOptions.EXCLUDE:
                        pass
                    elif on_partial == PartialChunkOptions.PAD:
                        yield vals + [pad] * (n - len(vals))
                    elif on_partial == PartialChunkOptions.ERROR:
                        raise PartialChunkException
                    return
                return
        yield vals

测试.py

import chunker

chunk_size = 3

for it in (range(100, 107),
          range(100, 109)):

    print("\nITERABLE TO CHUNK: {}".format(it))
    print("CHUNK SIZE: {}".format(chunk_size))

    for option in chunker.PartialChunkOptions.__members__.values():
        print("\noption {} used".format(option))
        try:
            for chunk in chunker.chunker(it, chunk_size, on_partial=option):
                print(chunk)
        except chunker.PartialChunkException:
            print("PartialChunkException was raised")
    print("")

test.py的输出


ITERABLE TO CHUNK: range(100, 107)
CHUNK SIZE: 3

option PartialChunkOptions.INCLUDE used
[100, 101, 102]
[103, 104, 105]
[106]

option PartialChunkOptions.EXCLUDE used
[100, 101, 102]
[103, 104, 105]

option PartialChunkOptions.PAD used
[100, 101, 102]
[103, 104, 105]
[106, None, None]

option PartialChunkOptions.ERROR used
[100, 101, 102]
[103, 104, 105]
PartialChunkException was raised


ITERABLE TO CHUNK: range(100, 109)
CHUNK SIZE: 3

option PartialChunkOptions.INCLUDE used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

option PartialChunkOptions.EXCLUDE used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

option PartialChunkOptions.PAD used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

option PartialChunkOptions.ERROR used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

这适用于v2/v3,可内联,基于生成器,仅使用标准库:

import itertools
def split_groups(iter_in, group_size):
    return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size))