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


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

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


当前回答

因为这里的每个人都在谈论迭代器。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]]

但如果您不想在内存上手下留情,您可以使用旧方法,首先使用iterutils.chunked存储完整列表。

其他回答

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

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)

我不喜欢按块大小拆分元素的想法,例如,脚本可以将101到3个块划分为[50,50,1]。为了我的需要,我需要按比例分配,保持秩序不变。首先我写了自己的剧本,效果很好,而且很简单。但我后来看到了这个答案,剧本比我的好,我想是这样的。这是我的脚本:

def proportional_dividing(N, n):
    """
    N - length of array (bigger number)
    n - number of chunks (smaller number)
    output - arr, containing N numbers, diveded roundly to n chunks
    """
    arr = []
    if N == 0:
        return arr
    elif n == 0:
        arr.append(N)
        return arr
    r = N // n
    for i in range(n-1):
        arr.append(r)
    arr.append(N-r*(n-1))

    last_n = arr[-1]
    # last number always will be r <= last_n < 2*r
    # when last_n == r it's ok, but when last_n > r ...
    if last_n > r:
        # ... and if difference too big (bigger than 1), then
        if abs(r-last_n) > 1:
            #[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7] # N=29, n=12
            # we need to give unnecessary numbers to first elements back
            diff = last_n - r
            for k in range(diff):
                arr[k] += 1
            arr[-1] = r
            # and we receive [3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2]
    return arr

def split_items(items, chunks):
    arr = proportional_dividing(len(items), chunks)
    splitted = []
    for chunk_size in arr:
        splitted.append(items[:chunk_size])
        items = items[chunk_size:]
    print(splitted)
    return splitted

items = [1,2,3,4,5,6,7,8,9,10,11]
chunks = 3
split_items(items, chunks)
split_items(['a','b','c','d','e','f','g','h','i','g','k','l', 'm'], 3)
split_items(['a','b','c','d','e','f','g','h','i','g','k','l', 'm', 'n'], 3)
split_items(range(100), 4)
split_items(range(99), 4)
split_items(range(101), 4)

和输出:

[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
[['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'], ['i', 'g', 'k', 'l', 'm']]
[['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h', 'i', 'g'], ['k', 'l', 'm', 'n']]
[range(0, 25), range(25, 50), range(50, 75), range(75, 100)]
[range(0, 25), range(25, 50), range(50, 75), range(75, 99)]
[range(0, 25), range(25, 50), range(50, 75), range(75, 101)]

因为这里的每个人都在谈论迭代器。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]]

但如果您不想在内存上手下留情,您可以使用旧方法,首先使用iterutils.chunked存储完整列表。

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

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

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