创建子列表

30

不同于列表扁平化的操作。

给定一个列表和一个长度 n,返回由长度为 n 的子列表组成的列表。

def sublist(lst, n):
    sub=[] ; result=[]
    for i in lst:
        sub+=[i]
        if len(sub)==n: result+=[sub] ; sub=[]
    if sub: result+=[sub]
    return result

一个例子:

如果列表如下:

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

并且 n 是:

3

返回:

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

有更简洁的写法吗?

顺带一提,在上述情况下将列表附加到列表时,哪种方式更受欢迎:

list1+=[list2]

或者:

list1.append(list2)

根据Summerfield的“Programming in Python 3”,假设它们是相同的?

谢谢。

7个回答

33

可以使用列表推导式构建这样一组列表:

In [17]: seq=[1,2,3,4,5,6,7,8]
In [18]: [seq[i:i+3] for i in range(0,len(seq),3)]
Out[18]: [[1, 2, 3], [4, 5, 6], [7, 8]]

还有一个叫做grouper惯用法

In [19]: import itertools
In [20]: list(itertools.izip_longest(*[iter(seq)]*3))
Out[20]: [(1, 2, 3), (4, 5, 6), (7, 8, None)]
但请注意,缺少的元素将用值None填充。izip_longest也可以接受一个fillvalue参数,以便更改为其他值而非None。
list1+=[list2] -- 注意这次的方括号 -- 等同于 list1.append(list2)。编写代码时,我最优先考虑的是可读性,而不是速度。因此,我会选择list1.append(list2)。然而,可读性是主观的,很可能受到您熟悉的习语的影响。但令人欣慰的是,在这种情况下,可读性和速度似乎是一致的:
In [41]: %timeit list1=[1,2,3]; list1.append(list2)
1000000 loops, best of 3: 612 ns per loop

In [42]: %timeit list1=[1,2,3]; list1+=[list2]
1000000 loops, best of 3: 847 ns per loop

感谢unutbu,使用列表推导式解决这个问题是多么的简单。这凸显了我需要学习它们的需求。关于+=和append的区别,我理解了它们之间的差异,注意到我将list1+=[list2]与list1.append(list2)进行了比较,而不是list1+=list2和list1.append(list2)。非常感谢你的答案/讨论。 - Michael Puckett
@Michael Puckett:抱歉,我误读了你问题的第二部分。正在编辑... - unutbu
谢谢unutbu -- append ftw. :) - Michael Puckett

11
以下是一个示例(其中x是您的列表):
 [x[i:i+3] for i in range(0, len(x), 3)]

这个可以很轻松地推广到 n!=3 的情况。

至于你的第二个问题,它们是等效的,所以我认为这只是一个风格问题。然而,请确保你不要 混淆了 appendextend


谢谢aix,列表推导式绝对是正确的方法。我感到惭愧,没有想到它,但安慰自己是我是Python新手。 - Michael Puckett

10

你听说过 boltons 吗?

Boltons 是一组纯Python实用工具,与标准库精神相同,但又明显缺失了某些功能。

它内置了你所需的东西,称为chunked

from boltons import iterutils

iterutils.chunked([1,2,3,4,5,6,7,8], 3)

输出:

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

而在boltons中更加吸引人的是它有一个迭代器叫做chunked_iter,可以将其chunked分块处理,因此您不需要在内存中存储整个内容。 很好用,对吧?


7

此函数可以接受任何类型的可迭代对象(不仅局限于已知长度的序列):

import itertools

def grouper(n, it):
    "grouper(3, 'ABCDEFG') --> ABC DEF G"
    it = iter(it)
    return iter(lambda: list(itertools.islice(it, n)), [])

print(list(grouper(3, [1,2,3,4,5,6,7,8,9,10])))
# [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

1

我认为这个分割函数可以满足你的需求(虽然它适用于任何迭代器而不仅仅是列表):

from itertools import islice

def take(n, it):
    "Return first n items of the iterable as a list"
    return list(islice(it, n))

def split(it, size):
    it = iter(it)
    size = int(size)
    ret = take(size, it)
    while ret:
        yield ret
        ret = take(size, it)

编辑:关于你的附言,我总是使用list.append(blah),因为它对我来说更符合惯用语,但我认为它们在功能上是等效的。


2
Django 的东西看起来并不必要。 - Xavier Combelle
@Xavier 嗯,我已经将它删除了(最初我是将其用作 Django 模板过滤器)。 - Gabriel Grant

1

对于某些特定情况,使用numpy包可能会很有用。在这个包中,你可以使用reshape例程:

import numpy as np
x = np.array([1,2,3,4,5,6])
np.reshape(x, (-1,3))

然而,如果列表的长度不是n的倍数,这种解决方案就不会填充您的列表。


Numpy 对于这个有点过头了。然而,了解这个功能还是很好的。谢谢 joker5。 - Michael Puckett

0

我知道,它看起来像是brainfuck,但它有效:

>>> a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
>>> n = 3
>>> [i for j in [[a[t:t+n] for x in a[:1:t+1] if (t%n)==False] for t in range(len(a))] for i in j]
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]]

>>> n = 4
>>> [i for j in [[a[t:t+n] for x in a[:1:t+1] if (t%n)==False] for t in range(len(a))] for i in j]
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15]]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接