Итерировать по всем измерениям, кроме d-го, любого увеличения :: multi_array

Довольно часто хочется применить операцию f() вдоль измерения d из Nмассив A, Это подразумевает цикл по всем остальным измерениям A, Я пытался выяснить, если boost::multi_array был способен на это. функция f(A) должен работать на всех разновидностях boost::multi_array, в том числе boost:multi_array_ref, boost::detail::multi_array::sub_array, а также boost::detail::multi_array::array_viewв идеале также для таких типов значений, как boost::multi_array_ref<T, NDims>::reference,

Лучшее, что я мог придумать, — это реализация reshape() функция, которая может использоваться для преобразования массива ND в трехмерный массив, так что рабочий размер всегда является средним. Вот f.hpp:

#include "boost/multi_array.hpp"#include <ostream>

using namespace boost;

typedef multi_array_types::index index_t;
typedef multi_array_types::index_range range;

template <template <typename, std::size_t, typename...> class Array,
typename T, std::size_t NDims, typename index_t, std::size_t NDimsNew>
multi_array_ref<T, NDimsNew>
reshape(Array<T, NDims>& A, const array<index_t, NDimsNew>& dims) {
multi_array_ref<T, NDimsNew> a(A.origin(), dims);
return a;
}

template <template <typename, std::size_t, typename...> class Array, typename T>
void f(Array<T, 1>& A) {
for (auto it : A) {
// do something with it
std::cout << it << " ";
}
std::cout << std::endl;
}

template <template <typename, std::size_t, typename...> class Array,
typename T, std::size_t NDims>
void f(Array<T, NDims>& A, long d) {
auto dims = A.shape();
typedef typename std::decay<decltype(*dims)>::type type;

// collapse dimensions [0,d) and (d,Ndims)
array<type, 3> dims3 = {
std::accumulate(dims, dims + d, type(1), std::multiplies<type>()),
dims[d],
std::accumulate(dims + d + 1, dims + NDims, type(1), std::multiplies<type>())
};

// reshape to collapsed dimensions
auto A3 = reshape(A, dims3);

// call f for each slice [i,:,k]
for (auto Ai : A3) {
for (index_t k = 0; k < dims3[2]; ++k) {
auto S = Ai[indices[range()][k]];
f(S);
}
}
}

template <template <typename, std::size_t, typename...> class Array,
typename T, std::size_t NDims>
void f(Array<T, NDims>& A) {
for (long d = NDims; d--; ) {
f(A, d);
}
}

Это тестовая программа test.cpp:

#include "f.hpp"
int main() {
boost::multi_array<double, 3> A(boost::extents[2][2][3]);
boost::multi_array_ref<double, 1> a(A.data(), boost::extents[A.num_elements()]);
auto Ajk = A[1];
auto Aik = A[boost::indices[range()][1][range()]];

int i = 0;
for (auto& ai : a) ai = i++;

std::cout << "work on boost::multi_array_ref" << std::endl;
f(a);

std::cout << "work on boost::multi_array" << std::endl;
f(A);

std::cout << "work on boost::detail::multi_array:sub_array" << std::endl;
f(Ajk);

std::cout << "work on boost::detail::multi_array:sub_array" << std::endl;
f(Aik);   // wrong result, since reshape() ignores strides!

//f(A[1]);   // fails: rvalue A[1] is boost::multi_array_ref<double, 3ul>::reference
}

Очевидно, что с этим подходом есть проблемы, а именно, когда срез передается f()так, что память больше не является смежной, что наносит ущерб реализации reshape(),

Похоже, что лучший (более похожий на C ++) способ состоит в том, чтобы создать составной итератор из итераторов, предоставляемых типами повышения, поскольку это автоматически позаботится о шагах, не связанных с единицей, по данному измерению. boost::detail::multi_array::index_gen выглядит актуально, но мне не совсем понятно, как это можно использовать для создания итератора по всем слайсам в измерении d, Есть идеи?

Замечания:

Уже есть похожие вопросы по SO, но ни один из них не был достаточно удовлетворительным для меня. Я не заинтересован в специализированных решениях для N = 3 или же N = 2, Это должно работать для любого N,

Обновить:

Вот эквивалент того, что я хочу в Python:

def idx_iterator(s, d, idx):
if len(s) == 0:
yield idx
else:
ii = (slice(None),) if d == 0 else xrange(s[0])
for i in ii:
for new_idx in idx_iterator(s[1:], d - 1, idx + [i]):
yield new_idx

def iterator(A, d=0):
for idx in idx_iterator(A.shape, d, []):
yield A[idx]

def f(A):
for d in reversed(xrange(A.ndim)):
for it in iterator(A, d):
print it
print

import numpy as np
A = np.arange(12).reshape((2, 2, 3))

print "Work on flattened array"f(A.ravel())

print "Work on array"f(A)

print "Work on contiguous slice"f(A[1])

print "Work on discontiguous slice"f(A[:,1,:])

То же самое должно быть как-то возможно, используя функциональность в index_gen.hpp, но я до сих пор не смог понять, как.

0

Решение

Хорошо, потратив значительное количество времени на изучение реализации boost::multi_arrayЯ теперь готов ответить на свой вопрос: нет, нигде нет положений boost::multi_array это позволило бы итерировать по любому, кроме первого измерения. Лучшее, что я мог придумать, — это создать итератор, который вручную управляет N-1 индексы, которые перебираются. Вот slice_iterator.hpp:

#include "boost/multi_array.hpp"
template <template <typename, std::size_t, typename...> class Array,
typename T, std::size_t NDims>
struct SliceIterator {
typedef Array<T, NDims> array_type;
typedef typename array_type::size_type size_type;
typedef boost::multi_array_types::index_range range;
typedef boost::detail::multi_array::multi_array_view<T, 1> slice_type;
typedef boost::detail::multi_array::index_gen<NDims, 1> index_gen;

array_type& A;
const size_type* shape;
const long d;
index_gen indices;
bool is_end = false;

SliceIterator(array_type& A, long d) : A(A), shape(A.shape()), d(d) {
int i = 0;
for (; i != d; ++i) indices.ranges_[i] = range(0);
indices.ranges_[i++] = range();
for (; i != NDims; ++i) indices.ranges_[i] = range(0);
}

SliceIterator& operator++() {
// addition with carry, excluding dimension d
int i = NDims - 1;
while (1) {
if (i == d) --i;
if (i < 0) {
is_end = true;
return *this;
}
++indices.ranges_[i].start_;
++indices.ranges_[i].finish_;
if (indices.ranges_[i].start_ < shape[i]) {
break;
} else {
indices.ranges_[i].start_ = 0;
indices.ranges_[i].finish_ = 1;
--i;
}
}
return *this;
}

slice_type operator*() {
return A[indices];
}

// fakes for iterator protocol (actual implementations would be expensive)
bool operator!=(const SliceIterator& r) {
return !is_end;
}

SliceIterator begin() {return *this;}
SliceIterator end()   {return *this;}
};

template <template <typename, std::size_t, typename...> class Array,
typename T, std::size_t NDims>
SliceIterator<Array, T, NDims> make_slice_iterator(Array<T, NDims>& A, long d) {
return SliceIterator<Array, T, NDims>(A, d);
}

// overload for rvalue references
template <template <typename, std::size_t, typename...> class Array,
typename T, std::size_t NDims>
SliceIterator<Array, T, NDims> make_slice_iterator(Array<T, NDims>&& A, long d) {
return SliceIterator<Array, T, NDims>(A, d);
}

Может использоваться как

for (auto S : make_slice_iterator(A, d)) {
f(S);
}

и работает для всех примеров в моем вопросе.

Я должен сказать, что boost::multi_arrayРеализация меня разочаровала: более 3700 строк кода, что должно быть чуть больше, чем просто ведение индекса. В частности, итераторы, которые предоставляются только для первого измерения, не имеют ничего общего с реализацией производительности. 3*N + 5 Сравнения, выполняемые на каждом шаге, чтобы определить, пришел ли итератор в конец (обратите внимание, что моя реализация выше позволяет избежать этой проблемы путем фальсификации operator!=()), что делает эту реализацию непригодной ни для чего, кроме массивов с доминирующим последним измерением, которое обрабатывается более эффективно. Более того, реализация не использует преимущества измерений, смежных в памяти. Вместо этого он всегда выполняет измерение за измерением для таких операций, как присвоение массива, тратя впустую значительные возможности оптимизации.

В итоге я нахожу numpyРеализация N-мерного массива гораздо более привлекательна, чем эта. Есть еще 3 наблюдения, которые говорят мне «Руки прочь» boost::multi_array:

  • Я не мог найти серьезных случаев использования для boost::multi_array где-нибудь в сети
  • Похоже, что в 2002 году разработка практически прекратилась
  • Этот (и подобные) вопросы по StackOverflow вряд ли вызвали какой-либо интерес 😉
3

Другие решения