关于C#:Constexpr行列式(二维std :: array)

Constexpr determinant (2 dimensional std::array)

我需要编写一个在编译时计算行列式的constexpr函数。最明显的解决方案是使用Laplace扩展。支持C14。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include
#include <utility>

constexpr int get_cofactor_coef(int i, int j) {
    return (i + j) % 2 == 0 ? 1 : -1;
}

template <int N>
constexpr int determinant(const std::array<std::array<int, N>, N>& a) {
    int det = 0;

    for (size_t i = 0u; i < N; ++i) {
        det += get_cofactor_coef(i, 1) * a[i][0] * determinant<N-1>(GET_SUBMATRIX_OF_A<N-1, I, J>(a);
    }

    return det;
}

template <>
constexpr int determinant<2>(const std::array<std::array<int, 2>, 2>& a) {
    return a[0][0] * a[1][1] - a[0][1] * a[1][0];
}

template <>
constexpr int determinant<1>(const std::array<std::array<int, 1>, 1>& a) {
    return a[0][0];
}

问题是我完全不知道如何编写GET_SUBMATRIX_OF_A

我知道我需要:

  • 生成一个序列(可能使用std::integer_sequence);
  • 从此序列中排除第i行;
  • 复制除第一列(第0列)以外的所有列;
  • 我的constexpr技能几乎不存在。尝试将a传递给另一个函数的尝试会导致奇怪的错误,例如错误:'*


    问题在于非const std::array<T, N>::operator[](返回T&)在C 17之前不是constexpr,这使得难以设置次要元素。

    但是,有一个转义符,即std::get(std::array&)是constexpr,对结果执行指针算术是完全合法的,因此我们可以重写

    1
    a[i]  // constexpr since C++17

    as

    1
    (&std::get<0>(a))[i]  // constexpr in C++14!!

    也就是说,我们使用std::get获得对数组第一个成员的constexpr引用,获取指向该数组的指针,并在指针和索引上使用内置的[]运算符。

    然后,二级数组成员访问a[i][j]变得非常丑陋,但仍然是constexpr (&std::get<0>((&std::get<0>(a))[i]))[j],这意味着我们可以将get_submatrix_of_a编写为普通的constexpr函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<std::size_t N>
    constexpr std::array<std::array<int, N - 1>, N - 1>
    get_submatrix_of_a(const std::array<std::array<int, N>, N>& a, int i, int j) {
        std::array<std::array<int, N - 1>, N - 1> r{};
        for (int ii = 0; ii != N - 1; ++ii)
            for (int jj = 0; jj != N - 1; ++jj)
                (&std::get<0>(((&std::get<0>(r))[ii])))[jj] = a[ii + (ii >= i ? 1 : 0)][jj + (jj >= j ? 1 : 0)];
        return r;
    }

    请记住,const std::array<T, N>::operator[]在C 14中已经是constexpr,因此我们不需要重写次要构造的RHS。


    这是一个示例实现。这样做的时间可能更短或更优雅,但这是一个起点。实际上,我刚刚意识到您的矩阵是正方形的,因此绝对可以在下面的代码中删除一些模板参数。

    正如我在评论中提到的那样,对于C 17及更高版本,很可能根本不需要这些。

    首先,让我们定义一些样板,让我们创建并索引一个遗漏一个值的序列(即您要跳过的行):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <utility>

    // Based on https://stackoverflow.com/a/32223343.
    template <size_t Offset, class T1, class T2>
    struct offset_sequence_merger;

    template <size_t Offset, size_t... I1, size_t... I2>
    struct offset_sequence_merger<Offset, std::index_sequence<I1...>, std::index_sequence<I2...>>
      : std::index_sequence<I1..., (Offset + I2)...>
    { };

    template <std::size_t Excluded, std::size_t End>
    using make_excluded_index_sequence = offset_sequence_merger<Excluded + 1,
      std::make_index_sequence<Excluded>,
      std::make_index_sequence<End - Excluded - 1>>;

    现在让我们使用它来提取子矩阵:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include
    template <class T, std::size_t N, std::size_t... Indices>
    constexpr std::array<T, sizeof...(Indices)> extract_columns (
        std::array<T, N> const & source, std::index_sequence<Indices...>) {
      return { source.at(Indices)... };
    }

    template <class T, std::size_t N>
    constexpr std::array<T, N - 1> drop_first_column (
        std::array<T, N> const & source) {
      return extract_columns(source, make_excluded_index_sequence<0, N>());
    }

    template <class T, std::size_t Rows, std::size_t Cols, std::size_t... RowIndices>
    constexpr auto create_sub_matrix (
        std::array<std::array<T, Cols>, Rows> const & source,
        std::index_sequence<RowIndices...>)
        -> std::array<std::array<T, Cols - 1>, sizeof...(RowIndices)> {
      return { drop_first_column(source.at(RowIndices))... };
    }

    template <std::size_t ExcludedRow, class T, std::size_t Rows, std::size_t Cols>
    constexpr auto create_sub_matrix (
        std::array<std::array<T, Cols>, Rows> const & source)
        -> std::array<std::array<T, Cols - 1>, Rows - 1> {
      return create_sub_matrix(source,
        make_excluded_index_sequence<ExcludedRow, Rows>());
    }

    最后,这是一些代码,显示上面的代码似乎已经完成了应做的工作。您可以在Wandbox上看到它的运行情况:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #include <iostream>
    #include <string>

    template <class T>
    void print_seq (std::integer_sequence< T > const & /* seq */) {
      std::cout << '\
    '
    ;
    }

    template <class T, T Head, T... Tail>
    void print_seq (std::integer_sequence<T, Head, Tail...> const & /* seq */) {
      std::cout << Head << ' ';
      print_seq(std::integer_sequence<T, Tail...>{});
    }

    template <class T, std::size_t N>
    void print_array (std::array<T, N> const & src) {
      std::string sep ="";
      for (auto const & e : src) {
        std::cout << sep << e;
        sep ="";
      }
      std::cout << '\
    '
    ;
    }

    template <class T, std::size_t N, std::size_t M>
    void print_matrix (std::array<std::array<T, N>, M> const & src) {
      for (auto const & row : src) { print_array(row); }
    }

    int main () {
      auto indexSeqA = make_excluded_index_sequence<0, 3>(); print_seq(indexSeqA);
      auto indexSeqB = make_excluded_index_sequence<1, 3>(); print_seq(indexSeqB);
      auto indexSeqC = make_excluded_index_sequence<2, 3>(); print_seq(indexSeqC);
      std::cout << '\
    '
    ;

      std::array<int, 3> arr = { 1, 7, 9 };
      print_array(arr); std::cout << '\
    '
    ;

      std::array<std::array<int, 3>, 3> matrix = {{
          { 0, 1, 2 }
        , { 3, 4, 5 }
        , { 6, 7, 8 }
      }};
      print_matrix(matrix); std::cout << '\
    '
    ;

      print_matrix(create_sub_matrix<0>(matrix)); std::cout << '\
    '
    ;
      print_matrix(create_sub_matrix<1>(matrix)); std::cout << '\
    '
    ;
    }

    希望这足以帮助您完全实现determinant功能。 (P.S .:无需在调用行列式时显式提供size_t模板参数,它将自动从其std :: array参数的大小中推导出来)。