Определитель Constexpr (2-мерный std :: array) - PullRequest
3 голосов
/ 27 февраля 2020

Мне нужно написать функцию constexpr, которая вычисляет определитель во время компиляции. Наиболее очевидным решением является использование расширения Лапласа. Поддерживается C ++ 14.

#include <array>
#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.

Я знаю, что мне нужно:

  1. Создать последовательность (вероятно, используя std::integer_sequence);
  2. Исключить из этой последовательности i-ю строку;
  3. Скопировать все, кроме первого (0-го) столбца;

Мои навыки constexpr практически отсутствуют. Попытка передать a другой функции приводит к странным ошибкам, таким как ошибка: '* & a' не является константным выражением.

Вся помощь очень ценится!

Ответы [ 2 ]

3 голосов
/ 27 февраля 2020

Проблема в том, что non- const std::array<T, N>::operator[] (возвращающий T&) не является constexpr до C ++ 17, что затрудняет установку элементов второстепенного.

Тем не менее, существует аварийный люк, который состоит в том, что std::get<I>(std::array&) является constexpr, и вполне допустимо выполнить арифметику указателя c для результата, поэтому мы можем переписать

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

как

(&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 функция:

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[] уже constexpr в C ++ 14, поэтому нам не нужно переписывать RHS второстепенного строительства.

0 голосов
/ 27 февраля 2020

Вот пример реализации. Это может быть возможно сделать еще короче или элегантнее, но это отправная точка. На самом деле, я только что понял, что ваши матрицы квадратные, поэтому определенно можно отбросить некоторые параметры шаблона в приведенном ниже коде.

Как я уже упоминал в своем комментарии, для C ++ 17 и выше, скорее всего, нет это вообще необходимо.

Сначала давайте определим шаблон, который мы создадим и индексируем последовательность с одним пропущенным значением (то есть строкой, которую вы хотите пропустить):

#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>>;

Теперь давайте использовать это для извлечения подматриц:

#include <array>
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 :

#include <iostream>
#include <string>

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

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 << '\n';
}

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 << '\n';

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

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

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

Надеюсь, этого достаточно, чтобы помочь вам полностью реализовать функцию determinant. (PS: нет необходимости явно предоставлять аргумент шаблона size_t определителю при его вызове, он будет автоматически выведен из размера его аргумента std :: array).

...