Python/Numpy快速选择列表中每个第n个块的方法

3

由于问题不清晰,感谢大家的回答!

我的原始问题是,我有一个列表 [1,2,3,4,5,6,7,8],我想选择每个大小为 x,间隔为 1 的块。所以如果我想选择大小为 2 的每个其他块,则结果将为 [1,2,4,5,7,8]。大小为三的块会给我 [1,2,3,5,6,7]。

我在切片方面进行了大量搜索,但找不到选择块而不是元素的方法。执行多个切片操作、拼接和排序似乎有点昂贵。输入可以是 python 列表或 numpy ndarray。提前致谢。


这很容易通过数组重塑来完成,但如果您从列表开始,则可能不会更快。 数组具有非微不足道的创建成本。 您使用的列表大小是多少? - hpaulj
你确定你的例子是正确的吗?我期望每个大小为2的块都是[1,2,5,6]。例如:[1,2,3,4,5,6,7,8] -> [[1,2], [3,4], [5,6], [7,8]] -> [[1,2], [5,6]] -> [1, 2, 5, 6] - Dunes
描述中存在歧义。我最初的理解是选择(1,2),跳过3,选择(4,5)等。或者它可以是选择(1,2),跳过(2,3),选择(2,4),跳过(3,4)等。或者就像你所说的那样,选择(1,2),跳过(3,4),选择(5,6)等。 - hpaulj
实际上这是一个图像通道,假设它的分辨率是1920 * 1080,我正在对其列进行切片。我已经更改了问题以消除混淆。Dunes的解释也很有趣。 - jonathan chen
6个回答

1
首先,您可以创建一个索引数组,然后使用np.in1d()函数来提取应该省略的索引,然后使用简单的not运算符获取必须保留的索引。最后,使用简单的布尔索引选择它们:
>>> a = np.array([1,2,3,4,5,6,7,8])
>>> range_arr = np.arange(a.size)
>>> 
>>> a[~np.in1d(range_arr,range_arr[2::3])]
array([1, 2, 4, 6, 8])

总体方法:

>>> range_arr = np.arange(np_array.size) 
>>> np_array[~np.in1d(range_arr,range_arr[chunk::chunk+1])]

现在可以工作了!现在不那么NumPythonic了,是吧?;) 真是个好主意。 - Divakar
1
另外,可能需要添加一条注释,指出这需要 a 是一个 NumPy 数组。 - Divakar
1
@Divakar 谢谢,是的,我也这么认为。实际上,这就是我第一眼看到它时想到的,但由于今晚我喝了太多咖啡,所以写得太复杂了 :-D。 - Mazdak

1
对我来说,你想跳过块之间的一个元素,直到输入列表或数组的末尾。以下是一种基于 np.delete 的方法,它删除了挤压在块之间的单个元素。
out = np.delete(A,np.arange(len(A)/(x+1))*(x+1)+x)

这里有另一种基于布尔索引的方法 -

L = len(A)
avoid_idx = np.arange(L/(x+1))*(x+1)+x
out = np.array(A)[~np.in1d(np.arange(L),avoid_idx)]

样例运行 -

In [98]: A = [51,42,13,34,25,68,667,18,55,32] # Input list

In [99]: x = 2

# Thus, [51,42,13,34,25,68,667,18,55,32]
                ^        ^         ^        # Skip these

In [100]: np.delete(A,np.arange(len(A)/(x+1))*(x+1)+x)
Out[100]: array([ 51,  42,  34,  25, 667,  18,  32])

In [101]: L = len(A)
     ...: avoid_idx = np.arange(L/(x+1))*(x+1)+x
     ...: out = np.array(A)[~np.in1d(np.arange(L),avoid_idx)]
     ...: 

In [102]: out
Out[102]: array([ 51,  42,  34,  25, 667,  18,  32])

聪明而快速的纯列表版本怎么样? - hpaulj

1
一个简单的列表解决方案。
>> ll = [1,2,3,4,5,6,7,8]
>> list(itertools.chain(*zip(ll[::3],ll[1::3])))
[1, 2, 4, 5, 7, 8]

至少对于大小为2的块,跳过块之间的一个值的情况。 数组ll []的切片确定块大小,切片步长确定块间距。
正如我在评论中提到的那样,问题描述存在一些歧义,因此在澄清之前,我不太敢进一步推广这个解决方案。
可能更容易推广numpy解决方案,但它们不一定更快。 转换为数组需要时间开销。
list(itertools.chain(*zip(*[ll[i::6] for i in range(3)])))

该函数生成长度为3的块,跳过3个元素。

zip(*) 是将列表的列表“转置”的惯用方式。

itertools.chain(*...) 是将列表的列表“扁平化”的惯用方式。

另一种选择是使用基于项目计数的条件的列表推导。

[v for i,v in enumerate(ll) if i%3]

方便地跳过每三个项目,与您的示例相同。 (0<(i%6)<4) 保留3,跳过3。

会说 itertools.chain.from_iterable(...) 是更符合惯用法的方式。从 itertools 中组合 compress 和 cycle 实际上非常快,并且非常容易推广。 - Dunes
我卡在了2.5版本,还没有from_iterable。或者说,在添加那个构造函数之前,我必须学习使用chain(* - hpaulj
我喜欢参数解包,但总觉得当你在解包到varargs参数时有点浪费...我感到足够沮丧,开始寻找一种替代chain(*的方法,并且很幸运地找到了。但愿也能有一个适用于zip的方法。 - Dunes

1

使用纯Python解决方案:

假设所需项目为:[yes, yes, no, yes, yes, no, ...]

编码更快,运行较慢:

data = [1, 2, 3, 4, 5, 6, 7, 8]
filtered = [item for i, item in enumerate(data) if i % 3 != 2]
assert filtered == [1, 2, 4, 5, 7, 8]

略微比较慢的编写速度,但运行更快:
from itertools import cycle, compress

data = [1, 2, 3, 4, 5, 6, 7, 8]
selection_criteria = [True, True, False]
filtered = list(compress(data, cycle(selection_criteria)))
assert filtered == [1, 2, 4, 5, 7, 8]

第二个示例运行时间是第一个示例的66%,并且也更清晰易懂,更容易更改选择条件。

0
一个简单的列表推导式就可以完成任务: [ L[i] for i in range(len(L)) if i%3 != 2 ] 对于大小为n的块 [ L[i] for i in range(len(L)) if i%(n+1) != n ]

0

这应该能解决问题:

step = 3
size = 2
chunks = len(input) // step
input = np.asarray(input)
result = input[:chunks*step].reshape(chunks, step)[:, :size]

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