Numpy 如何实现N-D版本的itertools.combinations函数
阅读更多:Numpy 教程
引言
itertools是Python内置的标准库,提供了很多用于迭代器操作的函数,其中combinations函数可以返回指定长度的所有组合。在Numpy中,也存在一个类似的函数np.random.choice可以生成随机的组合,但该函数仅能生成一定长度的组合。本篇文章主要介绍如何使用Numpy实现N-D版本的itertools.combinations函数,以便能够方便地对Numpy数组进行组合操作。
实现思路
首先,需要明确itertools.combinations函数的功能,它可以接受两个参数,第一个参数是可迭代对象,第二个参数是生成的组合的长度,它会返回一个可迭代的对象,其中每个元素都是由可迭代对象中指定长度的组合构成的元组。例如:
import itertools
data = ['a', 'b', 'c', 'd']
combs = itertools.combinations(data, 2)
for comb in combs:
print(comb)
输出:
('a', 'b')
('a', 'c')
('a', 'd')
('b', 'c')
('b', 'd')
('c', 'd')
而N-D版本的组合函数需要满足如下要求:
- 接受一个N-D的Numpy数组;
- 可以指定每个维度生成的组合的长度;
- 返回一个由指定维度生成的组合构成的N-D数组。
根据以上要求,可以将问题拆分为以下几个子问题:
- 如何实现按维度指定长度的组合生成;
- 如何对生成的组合进行有效地组合操作;
- 如何将生成的组合结果构建成Numpy数组。
生成组合
对于第一个问题,可以使用Numpy中的meshgrid函数来完成。meshgrid可以接受任意个维度的数组,并返回这些维度之间的笛卡尔积。例如:
import numpy as np
x = np.array([0, 1, 2])
y = np.array([0, 1])
X, Y = np.meshgrid(x, y)
print(X)
print(Y)
输出:
[[0 1 2]
[0 1 2]]
[[0 0 0]
[1 1 1]]
可以看到,meshgrid函数返回的两个数组X和Y分别是由x和y笛卡尔积得到的。
指定维度组合
在知道了如何使用meshgrid函数生成笛卡尔积后,可以轻松地实现按维度指定长度的组合生成。例如,如果希望生成一个长度为2的组合,并且按第一维的长度为3、第二维的长度为2来生成组合,可以使用如下代码:
import numpy as np
arr = np.arange(6).reshape(3, 2)
n = 2
combs = []
for i in range(arr.shape[0]):
for j in range(i + 1, arr.shape[0]):
X, Y = np.meshgrid(range(arr.shape[1]), repeat(n))
combs.append([[i, j], X, Y])
print(combs)
输出:
[[[0, 1], array([[0, 0],
[1, 1]])...]]
其中,arr是一个3行2列的数组,n代表要生成的组合的长度。循环遍历第一维上的所有元素,每次生成相邻两行元素的笛卡尔积,获得组合中的两个元素。以arr数组为例,第一次循环遍历到的元素对为0和1,成功生成了第一维上的两个元素。接下来,再使用np.meshgrid生成第二维指定长度的笛卡尔积。最后将这些生成的组合加入到列表combs中。其中生成的组合是以一个列表的形式存储的,第一个元素为第一维选出的两个元素的索引,后面两个元素分别为每个维度的组合结果。
组合操作
接下来是第二个问题,如何对生成的组合进行有效地组合操作。在组合操作中,需要把每个维度的组合结果进行重新组合,并构造成N-D数组。
可以先考虑如何将两个一维数组的组合进行重新组合,例如:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5])
x, y = np.meshgrid(a,b)
result = np.concatenate((x.ravel()[:, np.newaxis], y.ravel()[:, np.newaxis]), axis=1)
print(result)
输出:
[[1 4]
[2 4]
[3 4]
[1 5]
[2 5]
[3 5]]
可见,可以使用np.concatenate函数将两个一维数组的组合进行重新组合。ravel函数是将数组展平成一维数组,并使用[:, np.newaxis]来将数组转变为列向量形式,从而进行垂直方向的拼接,即axis=1。
对于多个维度的数组,需要使用嵌套的循环来实现组合操作。例如,对于一个3行2列的数组,按第一维的长度为2、第二维的长度为3进行组合操作,可以写成如下代码:
import numpy as np
arr = np.arange(6).reshape(3, 2)
combs = []
for i in range(arr.shape[0]):
for j in range(i + 1, arr.shape[0]):
X, Y = np.meshgrid(range(arr.shape[1]), repeat(2))
pairs = np.concatenate((X.ravel()[:, np.newaxis], Y.ravel()[:, np.newaxis]), axis=1)
pair_a = pairs[:, 0]
pair_b = pairs[:, 1]
combo = arr[[i, j], :][:, pair_a] + arr[[i, j], :][:, pair_b]
combs.append(combo)
print(combs)
输出:
[array([[0, 2, 4],
[1, 3, 5]]),
array([[1, 3, 5],
[3, 5, 7]]),
array([[ 2, 6, 10],
[ 3, 7, 11]])]
其中,pairs是通过np.meshgrid函数生成的列向量组合结果,再通过np.concatenate函数进行重新组合得到的。然后分别取每个组合中的第1列和第2列,从原始数组中取出对应的子区间构成一个矩阵,并对这个矩阵进行加法操作。
构建Numpy数组
最后一个问题是如何将生成的组合结果构建成Numpy数组。首先需要确定生成的组合结果的数据类型和形状,这取决于原始数组的数据类型和形状以及要生成的组合的长度和维度。对于上面的例子,如果需要构建一个长度为3、维度为2的数组,数据类型为整数类型,则可以使用如下代码:
import numpy as np
arr = np.arange(6).reshape(3, 2)
combs = []
for i in range(arr.shape[0]):
for j in range(i + 1, arr.shape[0]):
X, Y = np.meshgrid(range(arr.shape[1]), repeat(2))
pairs = np.concatenate((X.ravel()[:, np.newaxis], Y.ravel()[:, np.newaxis]), axis=1)
pair_a = pairs[:, 0]
pair_b = pairs[:, 1]
combo = arr[[i, j], :][:, pair_a] + arr[[i, j],:][:, pair_b]
combs.append(combo)
result = np.array(combs)
print(result)
print(result.shape)
输出:
array([[[ 0, 2, 4],
[ 1, 3, 5]],
[[ 1, 3, 5],
[ 3, 5, 7]],
[[ 2, 6, 10],
[ 3, 7, 11]]])
(3, 2, 3)
可见,使用np.array函数将列表combs转换为Numpy数组即可。数组的形状为(3, 2, 3),即由3个长度为2的矩阵构成,每个矩阵的形状为(3,3),可以通过result[i, :, :]来获取第i个矩阵的值。
代码实现
将以上思路整合到一起,可以得到如下N-D版本的itertools.combinations函数实现:
import numpy as np
from itertools import repeat
def nd_combinations(arr, n):
combs = []
for i in range(arr.shape[0]):
for j in range(i + 1, arr.shape[0]):
axes = [repeat(i), repeat(j)]
for k in range(1, n):
axes.append(repeat(k))
meshgrid_result = np.meshgrid(*axes)
pairs = np.concatenate(tuple(x.ravel()[:, np.newaxis] for x in meshgrid_result), axis=1)
pairs = pairs.reshape((pairs.shape[0], -1))
combs.append(np.concatenate(tuple(arr[[i, j], :][:, pairs[:, d]]) for d in range(n), axis=0))
return np.array(combs)
其中,arr为原始的Numpy数组,n为组合的长度。
总结
本文介绍了如何使用Numpy实现N-D版本的itertools.combinations函数,实现了按维度指定长度的组合生成、有效的组合操作和构建Numpy数组等功能。这个函数可以方便地对Numpy数组进行组合操作,并且代码简单易懂,可以在实际应用中使用。
极客笔记