当 n % k > 0 时,将 n 长列表分成 k 长块的简单习惯用法?

Posted

技术标签:

【中文标题】当 n % k > 0 时,将 n 长列表分成 k 长块的简单习惯用法?【英文标题】:Simple idiom to break an n-long list into k-long chunks, when n % k > 0? 【发布时间】:2011-08-10 02:44:54 【问题描述】:

在 Python 中,如果 nk (IOW, n % k == 0)。这是我最喜欢的方法(直接来自docs):

>>> 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 个副本一次。[iter(x)] * k 之前的 * 是必要的,因为 zip 期望将其参数作为“单独的”迭代器接收,而不是列表他们。)

我看到这个习语的主要缺点是,当 n 不是 k 的倍数(IOW,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) 成语来产生这个结果?

(当然,通过编写函数来解决这个问题并不难(例如,参见How do you split a list into evenly sized chunks? 或What is the most "pythonic" way to iterate over a list in chunks? 的许多优秀回复)。因此,这个问题的更准确的标题应该是“如何挽救map(None, *[iter(x)]*k)成语?”,但我想这会让很多读者感到困惑。)

令我震惊的是,将列表分成大小均匀的块是多么容易,而去除不需要的填充是多么困难(相比之下!),即使这两个问题似乎具有相当的复杂性。

【问题讨论】:

你问这是一个实际的原因,还是只是想看看能不能做到? 这不是***.com/questions/312443/…的复制品吗? @Ned Batchelder:我试图表明这篇文章是其后续/扩展(事实上,我在最后引用了相同的 *** 帖子)。另外,正如我在这篇文章末尾试图解释的那样,这篇文章不是关于解决分块问题(我引用的文章中给出了很好的解决方案),而是找出是否有一种简单的方法来扩展特定 Python 习语的用处。也许这些帖子需要一个不同的标题,但我能想到的所有帖子看起来都令人困惑...... 但是既然我们可以写一个函数来做到这一点,而且成语显然不明显,你为什么要这个? 【参考方案1】:
[x[i:i+k] for i in range(0,n,k)]

【讨论】:

【参考方案2】:
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]]

【讨论】:

【参考方案4】:

这个呢?这是一个不同的习语,但会产生您想要的结果:

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

【讨论】:

以上是关于当 n % k > 0 时,将 n 长列表分成 k 长块的简单习惯用法?的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer系列——67.剪绳子

动态规划贪心剪绳子

剑指offer:剪绳子

剪绳子 --剑指offer

剪绳子

剑指offer