从列表中取出每个第 n 个块

Posted

技术标签:

【中文标题】从列表中取出每个第 n 个块【英文标题】:Take every nth block from list 【发布时间】:2017-09-18 15:16:03 【问题描述】:

给定一个列表:

import string
a = list(string.ascii_lowercase)

返回每个 m 元素的 nth 块的 Pythonic 方式是什么?请注意,这与仅返回 every nth element 不同。

3 个元素的 3 个块中的第一个(取 3,跳过 6,取 3,跳过 6...):

['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

我可以这样理解:

import itertools
s1 = a[::9]
s2 = a[1::9]
s3 = a[2::9]    
res = list(itertools.chain.from_iterable(zip(s1,s2, s3)))

有没有更清洁的方法?

【问题讨论】:

【参考方案1】:

对于选择和跳过的固定顺序,您可以对窗口总长度取模(此处为 9)包装索引,并仅选择低于给定阈值 3 的索引:

lst = [x for i, x in enumerate(a) if i % 9 < 3]
print(lst)
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

你可以把它做成一个函数,让它使用起来更直观:

def select_skip(iterable, select, skip):
    return [x for i, x in enumerate(iterable) if i % (select+skip) < select]  

print(select_skip(a, select=3, skip=6))
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

【讨论】:

聪明的想法,但需要几个大脑周期才能看到“跳过”的正确性(6 == 9 - 3)。将此逻辑包装在辅助函数或生成器表达式中可能会更好。 @wim 是的,在函数中应该更直观。【参考方案2】:

也许只写一个简单的生成器是最易读的

def thinger(iterable, take=3, skip=6):
    it = iter(iterable)
    try:
        while True:
            for i in range(take):
                yield next(it)
            for i in range(skip):
                next(it)
    except StopIteration:
        return

即使输入是无限的或不可切片的(例如来自套接字的数据),这也具有工作的优势。

【讨论】:

我认为这可能是最平易近人的解决方案。我认为它在任何方面都不是最易读的。读取函数名通常比读取语法更好,它是未来程序员更有用的工具。【参考方案3】:

more_itertools 是一个第三方库,它实现了itertools recipes 和其他有用的工具,例如more_itertools.windowed

>  pip install more_itertools

代码

import string

from more_itertools import windowed, flatten


m, n = 3, 6
list(flatten(windowed(string.ascii_lowercase, m, step=m+n)))
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

windowed 每次迭代自然会步进一个位置。通过超越重叠(m),给定一个新的步骤,适当地确定窗口。

【讨论】:

【参考方案4】:

您可以使用一些generic "chunks" recipe:

windows = chunks(original_iter, n=3)

既然您已经按照自己的想法对数据进行了窗口化,请使用islice's second variant 来获得它的“步进”功能:

# flattens the list as well using chain
result = chain.from_iterable(islice(windows, 0, None, 2))

【讨论】:

您依赖于块和步幅大小之间的公倍数。取 9 个块,然后根据需要将它们切片可能会更干净。 也许,我只是想展示一般方法。我认为它可以很容易地改变。 这不太通用,不太容易改变。这就是我发表评论的原因。 我不同意,您说明了为自己稍微不同的用例更改它的方法。这里的重点是使用几乎标准的函数,因为我们很少做任何特别的事情。无需重新发明***。 假设用户现在想拿 4,跳过 5。或者拿 3,跳过 7。他们是怎么做到的?【参考方案5】:

您可以使用列表推导并创建一个函数来处理任何跳过、获取和列表值:

import string
import itertools
a = list(string.ascii_lowercase)
def everyNthBlock(a, take, skip):
  res = [a[i:i + take] for i in range(0, len(a) ,skip + take)]
  return list(itertools.chain(*res))

print(everyNthBlock(a, 3, 6))
#^^^^ => ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
print(everyNthBlock(a, 4, 7))
#^^^^ => ['a', 'b', 'c', 'd', 'l', 'm', 'n', 'o', 'w', 'x', 'y', 'z']

【讨论】:

【参考方案6】:

使用难以理解的列表理解:D

m, n = 3, 3
[elem for blockstart in range(0, len(a), m*n) for elem in a[blockstart:blockstart+n]]    
#> ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

【讨论】:

以上是关于从列表中取出每个第 n 个块的主要内容,如果未能解决你的问题,请参考以下文章

如何拆分每个第N个元素的Python列表

从选择列表中的引用表中选择第 N 行的一列

分块查找

python 从列表中取出一个字典元素

在每个第 n 个元素之后插入 Python 列表中的元素

通过放置一个列表中的每个第 n 个项目和另一个列表中的其他项目来合并 Python 中的列表?