Numpy ndarray.flatten(’F’)是否有逆
在处理数组时,我们常常需要将一个多维数组压平为一维。Numpy中提供了flatten和ravel方法来实现这个功能。其中,flatten方法按照以行优先(C风格)的顺序展开一个数组。而ravel方法返回一个展开的数组视图,以行优先(C风格)的顺序展开原始数组。
但是,如果我们需要按列展开数组(Fortran风格),我们就需要使用flatten(‘F’)方法。这个方法返回C风格展开的数组中元素在Fortran风格展开的数组中的对应位置。也就是说,flatten(‘F’)方法会先按照Fortran风格展开数组,然后返回这个展开后的数组在按行优先(C风格)展开后的位置上的元素。
那么问题来了,在压平一个多维数组为一维数组时,我们能否将一个按照Fortran风格展开得到的一维数组再还原回原来的多维数组呢?换一句话,flatten(‘F’)方法是否有逆?
阅读更多:Numpy 教程
寻找逆算法
在学术界,将一个多维数组压平成一维数组,然后在将这个一维数组还原回原来的多维数组,被称为一个数组变换问题array folding或unfold操作。对于普通的按C风格展开的数组,这个问题是可以简单地解决的。根据实验,一种比较简单的解决方式是,记录原始数组展开后每一个元素,在一维数组中的位置,之后就可以根据这些记录将一维数组还原为多维数组。但是按照Fortran风格展开后的数组无法通过这种方法还原。
那么是否有一种更加聪明的方式,可以通过一维数组中每一个元素的值的排列顺序推断出它在多维数组中的位置呢?对于按照Fortran风格展开后的数组来说,是否有类似于序列分组这一类的算法能够解决这个问题呢?在学术界,这个问题一直是个难题,迄今没有得到完全解决。但是我们还是能够通过一些变通的方式解决这个问题。
一种比较简单的方式是,将Fortran风格展开的数组转化为C风格展开的数组。实际的上,将C风格展开的数组转化为Fortran风格的过程也是通过改变元素的顺序实现的。因此我们可以通过一个数组变换函数和这个函数的逆函数相结合,实现Fortran风格展开数组的还原。
具体的方法是,先将按Fortran风格展开的数组按照C风格展开的顺序展开为一维数组,再将这个一维数组转化为C风格展开的数组。最后通过C风格展开数组变换的函数的参数,将这个数组重新转化为按行优先(C风格)展开的数组。这个算法的可行性是由于按Fortran风格展开的数组与按C风格展开的数组的元素位置关系是可以简单计算出来的。
具体实现
下面是一个将数组由fortran order变为c order的实现。
import numpy as np
def fortan2c(arr: np.ndarray, shape: tuple) -> np.ndarray:
'''
Args:
arr: The fortran order array.
shape: The fortran order array's shape.
Returns:
ret_arr: The c order array.
'''
ret_arr = np.zeros(arr.size, dtype=np.int64)
it = np.nditer(arr)
for i, x in enumerate(it):
idx = x
for j in range(i + 1, arr.ndim):
idx += shape[j-1] * it[j]
ret_arr[idx] = i
return ret_arr.reshape(arr.shape, order='F')
这个函数接收一个按照Fortran风格展开的数组和它的形状,将这个数组转化为C风格展开的一维数组ret_arr。在转化的过程中,我们记录了原始数组每个元素在C风格展开后的数组中的位置。最后通过reshape函数重新将其转化为我们需要的按列优先(Fortran风格)展开的数组。
下面是将数组由c order变为fortran order的实现。
def c2fortan(arr: np.ndarray, shape: tuple) -> np.ndarray:
'''
Args:
arr: The c order array.
shape: The c order array's shape.
Returns:
ret_arr: The fortran order array.
'''
idx_arr = np.arange(arr.size)
ret_arr = np.zeros(arr.size, dtype=arr.dtype)
for i, idx in enumerate(idx_arr):
it = np.nditer(np.unravel_index(idx, shape, order='C'), flags=['multi_index'])
idx_f = next(it)
for j in range(1, arr.ndim):
idx_f += shape[j-1] * next(it)
ret_arr[idx_f] = arr.flat[idx]
return ret_arr.reshape(shape, order='F')
这个函数和第一个函数的大体思路类似。它接收一个按照C风格展开的数组和它的形状,将这个数组转化为按列优先(Fortran风格)展开的一维数组ret_arr。在转化的过程中,我们记录了每一个元素在按Fortran风格展开后的数组中的位置。最后通过reshape函数重新将其转化为我们需要的按列优先(Fortran风格)展开的数组。
示例
下面是一个简单的示例,展示使用上述方法将按Fortran风格展开的数组还原成多维数组的过程。
arr = np.arange(24).reshape((3, 4, 2), order='F')
print(arr)
# [[[ 0 12]
# [ 3 15]
# [ 6 18]
# [ 9 21]]
# [[ 1 13]
# [ 4 16]
# [ 7 19]
# [10 22]]
# [[ 2 14]
# [ 5 17]
# [ 8 20]
# [11 23]]]
arr_flatten = arr.flatten('F')
print(arr_flatten)
# [ 0 1 2 12 13 14 3 4 5 15 16 17 6 7 8 18 19 20 9 10 11 21 22 23]
arr_c_order = fortan2c(arr_flatten, arr.shape)
print(arr_c_order)
# [[[ 0 3]
# [ 6 9]
# [12 15]
# [18 21]]
# [[ 1 4]
# [ 7 10]
# [13 16]
# [19 22]]
# [[ 2 5]
# [ 8 11]
# [14 17]
# [20 23]]]
arr_f_order = c2fortan(arr_c_order, arr.shape)
print(arr_f_order)
# [[[ 0 12]
# [ 3 15]
# [ 6 18]
# [ 9 21]]
# [[ 1 13]
# [ 4 16]
# [ 7 19]
# [10 22]]
# [[ 2 14]
# [ 5 17]
# [ 8 20]
# [11 23]]]
在上面的示例中,我们先利用Numpy生成了一个3×4×2的多维数组arr。将这个数组按Fortran风格展开后得到一个长度为24的一维数组arr_flatten。然后我们将这个按照Fortran风格展开的数组转化为C风格展开的数组arr_c_order。最后通过c2fortan函数将这个C风格展开的数组再转化为按Fortran风格展开的多维数组arr_f_order。在输出结果中,我们可以看到arr和arr_f_order是相同的数组。
总结
总的来说,Numpy的flatten(‘F’)方法并没有逆方法,但是我们通过一些类似于数组变换的技巧,可以通过将按Fortran风格展开的数组转化为按C风格展开的数组,再将其转化为按列优先(Fortran风格)展开的数组来解决这个问题。这个方法可以推广到其它的按Fortran风格展开得到的数组变换问题。在实际的应用中,需要根据具体的需求选择最合适的方法。