递归函数将任意函数应用于任意长度的N个数组,形成一个N维的交错多维数组

2024-04-26 06:31:53 发布

您现在位置:Python中文网/ 问答频道 /正文

给定N个输入数组,所有任意长度,我希望能够对每个数组的每个组合应用一个函数。你知道吗

例如:

给定输入数组:

[1, 2] [3, 4, 5] [6, 7, 8, 9]

以及一个返回N个元素乘积的函数

我希望能够将函数应用于这些元素的每个组合。在本例中,它将生成一个长度分别为2、3和4的三维数组。你知道吗

生成的数组如下所示:

[
    [
        [18, 21, 24, 27], 
        [24, 28, 32, 36], 
        [30, 35, 40, 45]
    ], 
    [
        [36, 42, 48, 54], 
        [48, 56, 64, 72], 
        [60, 70, 80, 90]
    ]
]

Tags: 函数元素数组乘积本例
3条回答

设N个数组的大小为n1,n2,…,nN。 然后,我们可以把这个问题分成两个数组的(N-1)计算。 在第一次计算中,计算n1,n2的乘积。 让输出为result1。 在第二次计算中,计算结果1,n3的乘积。 让输出为result2。 . . 在最后一次计算中,计算结果(N-2)的乘积nN。 让输出为结果(N-1)。你知道吗

你会知道结果1的大小是n2\n1, 结果2的大小是n3\n2\n1。 . . 你可以推断,结果(N-1)的大小是N(N)\N(N-1)\u。。。_n2*n1。你知道吗

现在我们给出两个数组:result(k-1)和arr(k)。 然后我们应该从结果(k-1)和arr(k)得到每个元素的乘积。 原因-结果(k-1)的大小为n(k-1)\n(k-2)\u。。。_n1,arr(k)的大小为n(k), 输出数组(结果(k))的大小应为n(k)\n(k-1)\u。。。_n1型。 这意味着这个问题的解是转置n(k)和结果(k-1)的点积。 所以,函数应该如下所示。你知道吗

productOfTwoArrays = lambda arr1, arr2: np.dot(arr2.T, arr1)

现在我们来解决第一个问题。 剩下的就是把它应用到所有N个数组。 所以这个解可能是迭代的。 让输入数组有N个数组。你知道吗

def productOfNArrays(Narray: list) -> list:
  result = Narray[0]
  N = len(Narray)

  for idx in range(1, N):
    result = productOfTwoArrays(result, Narray[idx])

  return result

整个代码可能在下面。你知道吗

def productOfNArrays(Narray: list) -> list:
  import numpy as np

  productOfTwoArrays = lambda arr1, arr2: np.dot(arr2.T, arr1)

  result = Narray[0]
  N = len(Narray)

  for idx in range(1, N):
    result = productOfTwoArrays(result, Narray[idx])

  return result

另一种方法是np.frompyfunc公司创建所需函数的ufunc。对于n个参数,这是与ufuncs.outer方法一起应用n-1次的方法。你知道吗

import numpy as np

def testfunc( a, b):
    return a*(a+b) + b*b

def apply_func( func, *args, dtype = np.float ):
    """ Apply func sequentially to the args
    """
    u_func = np.frompyfunc( func, 2, 1) # Create a ufunc from func
    result = np.array(args[0])
    for vec in args[1:]:
        result = u_func.outer( result, vec )  # apply the outer method of the ufunc
        # This returns arrays of object type. 
    return np.array(result, dtype = dtype) # Convert to type and return the result

apply_func(lambda x,y: x*y, [1,2], [3,4,5],[6,7,8,9] )

# array([[[18., 21., 24., 27.],
#         [24., 28., 32., 36.],
#         [30., 35., 40., 45.]],

#        [[36., 42., 48., 54.],
#         [48., 56., 64., 72.],
#         [60., 70., 80., 90.]]])

apply_func( testfunc, [1,2], [3,4,5],[6,7,8,9])

# array([[[ 283.,  309.,  337.,  367.],
#         [ 603.,  637.,  673.,  711.],
#         [1183., 1227., 1273., 1321.]],

#        [[ 511.,  543.,  577.,  613.],
#         [ 988., 1029., 1072., 1117.],
#         [1791., 1843., 1897., 1953.]]])

您可以通过广播来实现这一点:

import numpy as np


a = np.array([1, 2, 3])
b = np.array([4, 5])

c = a[None, ...] * b[..., None]
print(c)

输出:

[[ 4  8 12]
 [ 5 10 15]]

通过构造适当的切片来传递给操作数,这可以很容易地得到推广。你知道吗


编辑

这种概括的实现可以是:

import numpy as np


def apply_multi_broadcast_1d(func, dim1_arrs):
    n = len(dim1_arrs)
    iter_dim1_arrs = iter(dim1_arrs)
    slicing = tuple(
        slice(None) if j == 0 else None
        for j in range(n))
    result = next(iter_dim1_arrs)[slicing]
    for i, dim1_arr in enumerate(iter_dim1_arrs, 1):
        slicing = tuple(
            slice(None) if j == i else None
            for j in range(n))
        result = func(result, dim1_arr[slicing])
    return result


dim1_arrs = [np.arange(1, n + 1) for n in range(2, 5)]
print(dim1_arrs)
# [array([1, 2]), array([1, 2, 3]), array([1, 2, 3, 4])]
arr = apply_multi_broadcast_1d(lambda x, y: x * y, dim1_arrs)
print(arr.shape)
# (2, 3, 4)
print(arr)
# [[[ 1  2  3  4]
#   [ 2  4  6  8]
#   [ 3  6  9 12]]

#  [[ 2  4  6  8]
#   [ 4  8 12 16]
#   [ 6 12 18 24]]]

这里不需要递归,我也不确定它有什么好处。你知道吗


另一种方法是从Python函数生成^{}(如@TlsChris's answer)并使用其^{}方法:

import numpy as np


def apply_multi_outer(func, dim1_arrs):
    ufunc = np.frompyfunc(func, 2, 1)
    iter_dim1_arrs = iter(dim1_arrs)
    result = next(iter_dim1_arrs)
    for dim1_arr in iter_dim1_arrs:
        result = ufunc.outer(result, dim1_arr)
    return result

虽然这会得到相同的结果(对于1D阵列),但这比广播方法慢(根据输入大小从轻微到相当多)。你知道吗

此外,虽然apply_multi_broadcast_1d()仅限于一维输入,但apply_multi_outer()也适用于高维的输入数组。广播方法可以容易地适应更高维度的输入,如下所示。你知道吗


编辑2

apply_multi_broadcast_1d()概括为N-dim输入,包括将广播与函数应用分离,如下所示:

import numpy as np


def multi_broadcast(arrs):
    for i, arr in enumerate(arrs):
        yield arr[tuple(
            slice(None) if j == i else None
            for j, arr in enumerate(arrs) for d in arr.shape)]


def apply_multi_broadcast(func, arrs):
    gen_arrs = multi_broadcast(arrs)
    result = next(gen_arrs)
    for i, arr in enumerate(gen_arrs, 1):
        result = func(result, arr)
    return result

三者的基准表明apply_multi_broadcast()略慢于apply_multi_broadcast_1d(),但比apply_multi_outer()快:

def f(x, y):
    return x * y


dim1_arrs = [np.arange(1, n + 1) for n in range(2, 5)]
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast_1d(f, dim1_arrs)))
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast(f, dim1_arrs)))
# True
# True
%timeit apply_multi_broadcast_1d(f, dim1_arrs)
# 100000 loops, best of 3: 7.76 µs per loop
%timeit apply_multi_outer(f, dim1_arrs)
# 100000 loops, best of 3: 9.46 µs per loop
%timeit apply_multi_broadcast(f, dim1_arrs)
# 100000 loops, best of 3: 8.63 µs per loop

dim1_arrs = [np.arange(1, n + 1) for n in range(10, 16)]
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast_1d(f, dim1_arrs)))
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast(f, dim1_arrs)))
# True
# True
%timeit apply_multi_broadcast_1d(f, dim1_arrs)
# 100 loops, best of 3: 10 ms per loop
%timeit apply_multi_outer(f, dim1_arrs)
# 1 loop, best of 3: 538 ms per loop
%timeit apply_multi_broadcast(f, dim1_arrs)
# 100 loops, best of 3: 10.1 ms per loop

相关问题 更多 >