将一个长度为n的列表分成长度为k的块的简单方法,当n % k > 0时应该怎么做?

12

如果nk的倍数(即n % k == 0),在Python中很容易将一个长为n的列表分成k大小的块。以下是我最喜欢的方法(直接来自文档):

>>> k = 3
>>> n = 5 * k
>>> x = range(k * 5)
>>> zip(*[iter(x)] * k)
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]
(诀窍在于 [iter(x)] * k 会生成一个由 k 个指向同一迭代器的引用组成的列表,这个迭代器由 iter(x) 返回。然后 zip 调用这 k 份迭代器中的每一个恰好一次来生成每个块。在 n 不是 k 的倍数(也就是说,n % k > 0)时,我认为这种惯用法的主要缺陷是剩下的条目被省略了,例如:

>>> zip(*[iter(x)] * (k + 1))
[(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)]

有一种稍微打字时间长一点的替代方法,当 n % k == 0 时会产生与上述方法相同的结果,并且在 n % k > 0 时有更可接受的表现:

>>> map(None, *[iter(x)] * k)
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]
>>> map(None, *[iter(x)] * (k + 1))
[(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11), (12, 13, 14, None)]

至少,在这里,剩下的条目被保留了下来,但最后一块用None填充。如果只想要其他值作为填充,则itertools.izip_longest可以解决这个问题。

但是假设需要的解决方案是最后一块不填充,即

[(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11), (12, 13, 14)]

有没有一种简单的方法可以修改map(None, *[iter(x)]*k)惯用语以产生这个结果?

(当然,写一个函数来解决这个问题并不难(例如,参见如何将列表均匀分成多个小块?在Python中迭代列表的最"Pythonic"的方式是什么?等等)。因此,这个问题更准确的标题应该是“如何挽救 map(None,* [iter(x)] * k)惯用语?”,但我认为会让很多读者迷惑。)

我被如何将列表均匀分成多个小块的简易性所吸引,并且被如何去除不需要的填充的难度(相比之下!),即使这两个问题看起来具有可比性的复杂性。


你是出于实际原因在问这个问题,还是只是想看看它是否可行? - Winston Ewert
这不是 https://dev59.com/lnVC5IYBdhLWcg3wYQAp 的重复吗? - Ned Batchelder
@Ned Batchelder:我试图明确指出这篇文章是一个后续/扩展(事实上,我在结尾引用了同一个stackoverflow帖子)。 此外,正如我在本文结尾处试图解释的那样,这篇文章不太关注解决分块问题(关于它的好解决方案在我引用的帖子中已给出),而更多地是为了找到一种简单的方法来扩展特定的Python语法。也许这篇文章需要一个不同的标题,但是我能想到的所有标题看起来都很令人困惑... - kjo
但既然我们可以编写一个函数来完成这个任务,而且这种惯用法显然并不明显,那么你为什么要这样做呢? - Winston Ewert
4个回答

15
[x[i:i+k] for i in range(0,n,k)]

3
sentinal = object()
split = ( 
    (v for v in r if v is not sentinal) for r in
    izip_longest(*[iter(x)]*n, fillvalue=sentinal))

当然,更好的习惯是调用函数,因为这比其他做同样事情的方法更易读。

3

来自IPython的源代码:

def chop(seq,size):
    """Chop a sequence into chunks of the given size."""
    chunk = lambda i: seq[i:i+size]
    return map(chunk,xrange(0,len(seq),size))

最后返回的列表如果序列不能被平均分割,则会少于chunk个元素,基本上它会得到较短的一端而不抱怨。
>>> chop(range(12),3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
>>> chop(range(12),4)
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]
>>> chop(range(12),5)
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11]]
>>> chop(range(12),6)
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11]]

1

这个怎么样?虽然是不同的习语,但可以产生您想要的结果:

[x[i:i+k] for i in range(0,len(x),k)] #=> [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]]
[x[i:i+k] for i in range(0,len(x),k)] #=> [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14]]

或者如果你真的需要元组,使用tuple(x[i:i+k])而不是仅仅使用x[i:i+k]


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