Iterate over all but d-th dimension of any boost::multi_array
很多时候,人们想要沿着
我能想到的最好的方法是实现一个
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 56 57 | #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); } } |
这是测试程序
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 | #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 } |
显然,这种方法存在问题,即当将切片传递给
似乎更好(更像 C)的方法是从 boost 类型提供的迭代器中构造一个聚合迭代器,因为这会自动处理沿给定维度的非统一步幅。
注意:
SO 上已经有类似的问题,但没有一个让我很满意。我对
更新:
这相当于我在 Python 中想要的:
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 | 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 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,:]) |
使用
好的,在花了大量时间研究
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | #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); } |
可以作为
1 2 3 | for (auto S : make_slice_iterator(A, d)) { f(S); } |
适用于我的问题中的所有示例。
我必须说,
总之,我发现
的"放手"
-
我在网络上的任何地方都找不到
boost::multi_array 的任何严重用例 - 发展似乎在 2002 年基本停止
- StackOverflow 上的这个(和类似的)问题几乎没有引起任何兴趣 ;-)