C++ 迭代任何boost::multi_数组的d维以外的所有维

C++ 迭代任何boost::multi_数组的d维以外的所有维,c++,arrays,boost,iterator,boost-multi-array,C++,Arrays,Boost,Iterator,Boost Multi Array,通常,人们希望沿N-维数组A的维度d应用操作f()。这意味着在A的所有剩余维度上循环。我试图弄清楚boost::multi_array是否能够做到这一点。函数f(A)应适用于所有类型的boost::multi_array,包括boost:multi_array\u ref,boost::detail::multi_array::sub_array,以及boost::detail::multi_array::array\u view,理想情况下也适用于诸如boost::multi_array\u

通常,人们希望沿
N
-维数组
A
的维度
d
应用操作
f()。这意味着在
A
的所有剩余维度上循环。我试图弄清楚
boost::multi_array
是否能够做到这一点。函数
f(A)
应适用于所有类型的
boost::multi_array
,包括
boost:multi_array\u ref
boost::detail::multi_array::sub_array
,以及
boost::detail::multi_array::array\u view
,理想情况下也适用于诸如
boost::multi_array\u ref::ref::ref::reference
等右值类型

我能想到的最好的方法是实现一个
restrape()
函数,该函数可用于将ND阵列重塑为3D阵列,从而使工作尺寸始终处于中间位置。这里是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);
    }
}
#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
}
#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);
}
显然,这种方法存在问题,即当一个片被传递到
f()
,这样内存就不再是连续的,这就破坏了
重塑()
的实现

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

注:

已经有类似的问题了,但没有一个让我很满意。我对
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\u gen.hpp
中的功能也应该可以实现同样的效果,但我仍然不知道如何实现。

好的,在花了大量时间研究了
boost::multi\u array
的实现之后,我现在准备回答我自己的问题:不,在
boost::multi_array
中没有任何条款允许一个人沿着第一维度以外的任何维度进行迭代。我能想到的最好方法是构造一个迭代器,手动管理正在迭代的
N-1
索引。下面是切片迭代器.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);
    }
}
#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
}
#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);
}
并且适用于我问题中的所有示例

我必须说,
boost::multi_array
的实现让我非常失望:超过3700行代码,这应该只是一点索引管理。尤其是仅为第一维度提供的迭代器,与性能实现没有任何关系:实际上,在每个步骤中都会执行多达
3*N+5
的比较,以确定迭代器是否已经到达末尾(注意,我上面的实现通过假装
操作符!=()
)避免了这个问题),这使得此实现不适用于除最后一个维度占主导地位的数组之外的任何对象,而最后一个维度的处理效率更高。此外,该实现没有利用内存中相邻的维度。相反,它总是逐维度进行操作,如数组分配,浪费了大量的opti最大化机会

总之,我发现
numpy
实现的N维数组比这一个更有说服力。还有3个观察结果告诉我,boost::multi_数组的“放手”:

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

好的,在花了大量时间研究了
boost::multi_array
的实现之后,我现在准备回答我自己的问题:不,在
boost::multi_array
中没有任何条款允许一个人沿着第一维度以外的任何维度进行迭代。我能想到的最好方法是构造一个迭代器,手动管理正在迭代的
N-1
索引。下面是切片迭代器.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);
    }
}
#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
}
#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);
}
并且适用于我问题中的所有示例

我必须说,
boost::multi_array
的实现让我非常失望:超过3700行代码,这应该只是一点索引管理。尤其是仅为第一维度提供的迭代器,与性能实现没有任何关系:实际上,在每个步骤中都会执行多达
3*N+5
的比较,以确定迭代器是否已经到达末尾(注意,我上面的实现通过假装
操作符!=()
)避免了这个问题),这使得此实现不适用于除最后一个维度占主导地位的数组之外的任何对象,而最后一个维度的处理效率更高。此外,该实现没有利用内存中相邻的维度。相反,它总是逐维度进行操作,如数组分配,浪费了大量的opti最大化机会

总之,我发现
numpy
实现的N维数组比这一个更有说服力。还有3个观察结果告诉我,boost::multi_数组的“放手”:

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

您看到下面的解释了吗?特别是“例如,如果索引是一个对象