c++ 如何将boost::multi_array中指向元素的指针转换为它的索引?

r6hnlfcb  于 2023-03-05  发布在  其他
关注(0)|答案(2)|浏览(121)

在我的程序中,我使用boost::multi_array,有时候将容器中元素的指针转换成索引是很重要的,例如,检查元素是否在数组的任何维度上都不在数组的边界上。

boost::multi_array<int, 2> arr2d(boost::extents[10][10]);
    const auto data = arr2d.data();
    const auto end = data + arr2d.num_elements();

    for ( auto it = data; it != end; ++it )
    {
        [[maybe_unused]] int v = *it;
        // how to convert it into x and y indices in arr2d?
    }

boost::multi_array中是否有内置的有效方法来进行指针到索引的转换?或者唯一的可能是手动划分相对于维度上数据开始的偏移量?

c0vxltue

c0vxltue1#

这个工具似乎不在库中。这有点遗憾,但从库的接口/用例来看是有意义的。
我想出了这个解决方案,它考虑了所有的存储顺序/方向,和基本索引:

template <typename MultiArray,
          typename Element = typename MultiArray::element_type>
auto get_coords(Element const* el, MultiArray const& arr)
{
    using index            = typename MultiArray::index;
    using size_type        = typename MultiArray::size_type;
    auto constexpr NumDims = MultiArray::dimensionality;

    auto raw_index = std::distance(arr.data(), el);
    assert(raw_index >= 0 && size_t(raw_index) < arr.num_elements());

    std::array<index, NumDims> coord {0};
    auto shape    = arr.shape();
    auto strides  = arr.strides();
    auto bases    = arr.index_bases();
    auto& storage = arr.storage_order();

    for (size_type n = 0; n < NumDims; ++n) {
        auto dim = storage.ordering(NumDims - 1 - n); // reverse order
        //fmt::print("dim: {} stride: {}\n", dim, strides[dim]);
        coord[dim] = raw_index / strides[dim];
        raw_index -= coord[dim] * strides[dim];
    }

    for (size_type dim = 0; dim < NumDims; ++dim) {
        coord[dim] += bases[dim];
        if (!storage.ascending(dim))
            coord[dim] += shape[dim] - 1;
    }

    return coord;
}

因为这是太多的逻辑不测试它我想出了一个严格的测试:

void test(auto arr) {
    iota_n(arr.data(), 1, arr.num_elements());
    fmt::print("\n{} -> {}\n", arr, map(arr));
    check_all(arr);
}

这将导致按内存顺序打印每个元素的Map坐标:

template <typename MultiArray> auto map(MultiArray const& arr)
{
    using Coord =
        std::array<typename MultiArray::index, MultiArray::dimensionality>;

    std::vector<Coord> v;
    for (size_t n = 0; n<arr.num_elements(); ++n) {
        v.push_back(get_coords(arr.data() + n, arr));
    }

    return v;
}

并且还检查所有元素:

void check_element(auto const& el, auto& arr) {
    auto coord = get_coords(&el, arr);
    //fmt::print("{} at {}\n", el, coord);

    // assert same value at same address
    auto& check = deref(arr, coord);
    assert(el == check);
    assert(&el == &check);
    s_elements_checked += 1;
}

void check_all(int    const& el, auto& arr) { check_element(el, arr); }
void check_all(double const& el, auto& arr) { check_element(el, arr); }

void check_all(auto const& rank, auto& arr) {
    for (auto&& rank_or_val : rank) check_all(rank_or_val, arr);
}
void check_all(auto const& arr) {
    for (auto&& rank : arr) check_all(rank, arr);
}

然后我在各种测试矩阵上运行它:

int main() {
    // default
    test(boost::multi_array<int, 2>{boost::extents[3][2], boost::c_storage_order()});
    // FORTRAN
    test(boost::multi_array<int, 2>{boost::extents[3][2], boost::fortran_storage_order()});

    {
        boost::multi_array<int, 3> based{boost::extents[3][2][3], boost::fortran_storage_order()};
        // using standard bases (0)
        test(based);

        // using non-standard bases
        based.reindex(std::array<int, 3> {50,-20,100});
        test(based);
    }

    {
        // with custom storage ordering of dimensions, including descending
        std::array<int, 3> order{2, 0, 1};
        std::array<bool, 3> ascending {false,true,true};
        boost::multi_array<int, 3> custom{
            boost::extents[3][2][4],
            boost::general_storage_order<3>(order.data(), ascending.data())};
        custom.reindex(std::array<int, 3> {100,100,100});
        test(custom);
    }

    fmt::print("Checked a total of {} elements, verified ok\n", s_elements_checked);
}

这个打印
{{1、2}、{3、4}、{5、6}}-〉{{0、0}、{0、1}、{1、0}、{1、1}、{2、0}、{2、1}}
{{1、4}、{2、5}、{3、6}}-〉{{0、0}、{1、0}、{2、0}、{0、1}、{1、1}、{2、1}}
{{{1、7、13}、{4、10、16}}、{{2、8、14}、{5、11、17}}、{{3、9、15}、{6、12、18}}-〉{{0、0、0}、{1、0、0}、{2、0、0}、{1、1、0}、{2、1、0}、{0、0、1}、{1、0、1}、{2、0、1}、{0、1、1}、{1、1、1}、{2、1、1}、{0、0、2}、{1、0、2}、{2、0、2}、{0、1、2}、{1、1、2}、{2、1、2}}
{{{1、7、13}、{4、10、16}}、{{2、8、14}、{5、11、17}}、{{3、9、15}、{6、12、18}}-〉{{50、-20、100}、{51、-20、100}、{52、-20、100}、{50、-19、100}、{51、-19,100},{52,-19,100},{50,-20,101},{51,-20,101},{52,-20,101},{50,-19,101},{51,-19,101},{52,-19,101},{50,-20,102},{51,-20,102}、{52、-20、102}、{50、-19、102}、{51、-19、102}、{52、-19、102}}
{{{9、10、11、12}、{21、22、23、24}、{{5、6、7、8}、{17、18、19、20}、{{1、2、3、4}、{13、14、15、16}}-〉{102、100、100}、{102、100、101}、{102、100、102}、{102、100、103}、{101、100、100}、{101、100、101}、{101、100、102}、{101、100、103}、{100、100、100}、{100、100、101}、{100、100、102}、{100、100、103}、{102、101、100}、{102、101、101}、{102、101、102}、{102、101、103}、{101、101、100}、{101、101、101}、{101、101、102}、{101、101、103}、{100、101、100}、{100、101、101}、{100、101、102}、{100、101、103}}共检查了72个元素,验证结果正常

完整列表

    • 一个
#include <boost/multi_array.hpp>
#include <fmt/ranges.h>

template <typename MultiArray,
        typename Element = typename MultiArray::element_type>
auto get_coords(Element const* el, MultiArray const& arr)
{
    using index            = typename MultiArray::index;
    using size_type        = typename MultiArray::size_type;
    auto constexpr NumDims = MultiArray::dimensionality;

    auto raw_index = std::distance(arr.data(), el);
    assert(raw_index >= 0 && size_t(raw_index) < arr.num_elements());

    std::array<index, NumDims> coord {0};
    auto shape    = arr.shape();
    auto strides  = arr.strides();
    auto bases    = arr.index_bases();
    auto& storage = arr.storage_order();

    for (size_type n = 0; n < NumDims; ++n) {
        auto dim = storage.ordering(NumDims - 1 - n); // reverse order
        //fmt::print("dim: {} stride: {}\n", dim, strides[dim]);
        coord[dim] = raw_index / strides[dim];
        raw_index -= coord[dim] * strides[dim];
    }

    for (size_type dim = 0; dim < NumDims; ++dim) {
        coord[dim] += bases[dim];
        if (!storage.ascending(dim))
            coord[dim] += shape[dim] - 1;
    }

    return coord;
}

template <typename MultiArray> auto map(MultiArray const& arr)
{
    using Coord =
        std::array<typename MultiArray::index, MultiArray::dimensionality>;

    std::vector<Coord> v;
    for (size_t n = 0; n<arr.num_elements(); ++n) {
        v.push_back(get_coords(arr.data() + n, arr));
    }

    return v;
}

#include <boost/algorithm/cxx11/iota.hpp>
using boost::algorithm::iota_n;

auto& deref(auto const& arr, std::array<long, 1> coord) { return arr[coord[0]]; }
auto& deref(auto const& arr, std::array<long, 2> coord) { return arr[coord[0]][coord[1]]; }
auto& deref(auto const& arr, std::array<long, 3> coord) { return arr[coord[0]][coord[1]][coord[2]]; }
auto& deref(auto const& arr, std::array<long, 4> coord) { return arr[coord[0]][coord[1]][coord[2]][coord[3]]; }

static int s_elements_checked = 0;

void check_element(auto const& el, auto& arr) {
    auto coord = get_coords(&el, arr);
    //fmt::print("{} at {}\n", el, coord);

    // assert same value at same address
    auto& check = deref(arr, coord);
    assert(el == check);
    assert(&el == &check);
    s_elements_checked += 1;
}

void check_all(int    const& el, auto& arr) { check_element(el, arr); }
void check_all(double const& el, auto& arr) { check_element(el, arr); }

void check_all(auto const& rank, auto& arr) {
    for (auto&& rank_or_val : rank) check_all(rank_or_val, arr);
}
void check_all(auto const& arr) {
    for (auto&& rank : arr) check_all(rank, arr);
}

void test(auto arr) {
    iota_n(arr.data(), 1, arr.num_elements());
    fmt::print("\n{} -> {}\n", arr, map(arr));
    check_all(arr);
}

int main() {
    // default
    test(boost::multi_array<int, 2>{boost::extents[3][2], boost::c_storage_order()});
    // FORTRAN
    test(boost::multi_array<int, 2>{boost::extents[3][2], boost::fortran_storage_order()});

    {
        boost::multi_array<int, 3> based{boost::extents[3][2][3], boost::fortran_storage_order()};
        // using standard bases (0)
        test(based);

        // using non-standard bases
        based.reindex(std::array<int, 3> {50,-20,100});
        test(based);
    }

    {
        // with custom storage ordering of dimensions, including descending
        std::array<int, 3> order{2, 0, 1};
        std::array<bool, 3> ascending {false,true,true};
        boost::multi_array<int, 3> custom{
            boost::extents[3][2][4],
            boost::general_storage_order<3>(order.data(), ascending.data())};
        custom.reindex(std::array<int, 3> {100,100,100});
        test(custom);
    }

    fmt::print("Checked a total of {} elements, verified ok\n", s_elements_checked);
}
g2ieeal7

g2ieeal72#

虽然@sehe的答案是正确的,也是相当一般的,但下面是针对您的特定情况的解决方案:

boost::multi_array<int, 2> arr2d(boost::extents[10][10]);
    const auto data = arr2d.data();
    const auto end = data + arr2d.num_elements();

    for ( auto it = data; it != end; ++it )
    {
        [[maybe_unused]] int v = *it;
        auto x = (it - data) / arr2d.size();  // you can use arr2d.strides()[0] here, but it is not the main point
        auto y = (it - data) % arr2d.size();

        assert( &arr2d[x][y] == it );
    }

这也显示了这种方法的一个缺陷,即整数除法和取模(尽管可能融合在一个运算中)通常是CPU中相当昂贵的运算(与整数加法和乘法相比)。
所以,如果循环中的主操作很便宜,那么你基本上要为这些昂贵的代数操作付出代价(Sehe的解决方案不是灵丹妙药,它也有这个缺点,看看他代码中间的除法)。
在这种情况下,您必须做的是,如果您确实需要重新构建索引,最好首先从索引开始)。

boost::multi_array<int, 2> arr2d(boost::extents[10][10]);
    const auto data = arr2d.data();
    const auto end = data + arr2d.num_elements();

    for (auto x = 0; x != arr2d.shape()[0]; ++x) {
        for( auto y = 0; y != arr2d.shape()[1]; ++y) {
           auto it = &arr2d[x][y];
           [[maybe_unused]] int v = *it;

        }
    }

这里的双循环不是主要的,它最终可以被std::cartesian_product或者一个等价的自定义范围所取代,关键是从索引到内存中的位置比反向操作要便宜。

相关问题