迭代任何boost :: multi_array的除了d维之外的所有维度

时间:2014-05-20 11:25:11

标签: c++ arrays boost iterator boost-multi-array

通常,人们希望在f()维数组d的维度N上应用操作A。这意味着循环A的所有剩余维度。我试图找出boost::multi_array能否做到这一点。函数f(A)应适用于boost::multi_array的各种类型,包括boost:multi_array_refboost::detail::multi_array::sub_arrayboost::detail::multi_array::array_view,理想情况下也适用于{{1}等rvalue类型}。

我能想到的最好的是boost::multi_array_ref<T, NDims>::reference函数的实现,可用于将ND数组重新整形为3D数组,这样工作维度始终是中间的。这是reshape()

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()的实现失败。

似乎更好(更像C ++的)方法是从boost类型提供的迭代器中构造一个聚合迭代器,因为这会自动处理给定维度的非统一步幅。 reshape()看起来很相关,但我不清楚如何使用它来在维度boost::detail::multi_array::index_gen中的所有切片上创建迭代器。有什么想法吗?

注意:

在SO上也有类似的问题,但没有一个对我很满意。我对dN = 3的专业解决方案不感兴趣。它适用于任何N = 2

更新

这相当于我想要的Python:

N

同样应该可以使用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,:]) 中的功能,但我仍然无法弄清楚如何。

1 个答案:

答案 0 :(得分:3)

好的,在花了大量时间研究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的“Hands Off”:

  • 我无法在网络上的任何地方找到boost::multi_array的任何严重用例
  • 发展似乎在2002年基本停止了
  • 关于StackOverflow的这个(和类似的)问题几乎没有引起任何兴趣; - )