高效生成累积乘积三角矩阵

1 投票
4 回答
77 浏览
提问于 2025-04-14 16:01

假设我们有一个一维的向量,比如说 [a b c d]

接下来,我们要根据这个向量构建一个特定的矩阵。

a    0   0  0
ab   b   0  0
abc  bc  c  0
abcd bcd cd d

我现在写的代码可以完成这个任务,但看起来很复杂,而且里面有一个循环,其实这个循环是完全不需要的。

import numpy as np

v = np.array([1, 2, 3])
n = len(v)
matrix = np.zeros((n,n))
for i in range(n):
    matrix [i,:i+1] = np.flip(np.cumprod(np.flip(v[:i+1])))

print(matrix)
# [[1. 0. 0.]
#  [2. 2. 0.]
#  [6. 6. 3.]]

那么,我该怎么把它变得更简单呢?

4 个回答

0

我不太确定你能否避免使用for循环。如果你想让代码看起来更好,可以使用 np.lib.stride_tricks.sliding_window_view 来创建你想要的步骤,然后用 cumprod 来计算乘积,最后用 np.diagflat 来构建最终下三角矩阵的一部分,每个对角线偏移量都有一部分。把这些部分加起来就能得到最终的矩阵,因为每个部分在其他元素上都是零。

import numpy as np
from numpy.lib.stride_tricks import sliding_window_view

arr = np.array([2,3,5,7])
m = sum(
    np.diagflat(sliding_window_view(arr, i+1).prod(axis=1), -i)
    for i in range(len(arr))
)

m
# returns:
array([[  2,   0,   0,   0],
       [  6,   3,   0,   0],
       [ 30,  15,   5,   0],
       [210, 105,  35,   7]])
1
抱歉,我无法处理这个请求。
2

如果你关心速度,可以考虑使用

from numba import njit

@njit
def cumprod_triangular_numba(arr):
    out = np.zeros((arr.size, arr.size), dtype=np.int64)

    for col in range(arr.size):
        p = 1
        for row in range(col, arr.size):
            p *= arr[row]
            out[row, col] = p

    return out

基准测试:

import numpy as np
import perfplot
from numba import njit, prange
from numpy.lib.stride_tricks import sliding_window_view


def cumprod_triangular_orig(arr):
    n = len(arr)
    matrix = np.zeros((n, n))
    for i in range(n):
        matrix[i, : i + 1] = np.flip(np.cumprod(np.flip(arr[: i + 1])))
    return matrix


def cumprod_triangular_james(arr):
    return sum(
        np.diagflat(sliding_window_view(arr, i + 1).prod(axis=1), -i)
        for i in range(len(arr))
    )


def cumprod_triangular_onyambu_1(arr):
    u = arr.cumprod()
    return u[:, None] / np.r_[1, u[:-1]] * np.tri(arr.size, dtype=int)


def cumprod_triangular_onyambu_2(arr):
    a = np.triu(arr).T
    i1 = a == 0
    a[i1] = 1
    return np.where(i1, 0, a.cumprod(0))


def cumprod_triangular_onyambu_3(arr):
    a = np.triu(arr).T
    return np.where(a, a, 1).cumprod(0) * np.tri(arr.size, dtype=int)


@njit
def cumprod_triangular_numba(arr):
    out = np.zeros((arr.size, arr.size), dtype=np.int64)

    for col in prange(arr.size):
        p = 1
        for row in range(col, arr.size):
            p *= arr[row]
            out[row, col] = p

    return out


@njit(parallel=True)
def cumprod_triangular_numba_parallel(arr):
    out = np.zeros((arr.size, arr.size), dtype=np.int64)

    for col in prange(arr.size):
        p = 1
        for row in range(col, arr.size):
            p *= arr[row]
            out[row, col] = p

    return out


arr = np.array([2, 3, 5, 7])

assert np.allclose(cumprod_triangular_numba(arr), cumprod_triangular_orig(arr))
assert np.allclose(cumprod_triangular_numba_parallel(arr), cumprod_triangular_orig(arr))
assert np.allclose(cumprod_triangular_james(arr), cumprod_triangular_orig(arr))
assert np.allclose(cumprod_triangular_onyambu_1(arr), cumprod_triangular_orig(arr))
assert np.allclose(cumprod_triangular_onyambu_2(arr), cumprod_triangular_orig(arr))
assert np.allclose(cumprod_triangular_onyambu_3(arr), cumprod_triangular_orig(arr))


np.random.seed(0)

perfplot.show(
    setup=lambda n: np.random.randint(1, 2, size=n, dtype=np.int64),
    kernels=[
        cumprod_triangular_orig,
        cumprod_triangular_james,
        cumprod_triangular_numba,
        cumprod_triangular_numba_parallel,
        cumprod_triangular_onyambu_1,
        cumprod_triangular_onyambu_2,
        cumprod_triangular_onyambu_3,
    ],
    labels=["orig", "james", "numba", "numba_parallel", "o_1", "o_2", "o_3"],
    n_range=[3, 5, 10, 15, 20, 25, 50, 75, 100, 200, 500, 1000],
    xlabel="N",
    logx=True,
    logy=True,
    equality_check=np.allclose,
)

在我的电脑上(AMD 5700x),生成了这个图:

enter image description here

撰写回答