如何将指向 boost::multi_array 中元素的指针转换为其索引?
How to convert a pointer to an element in boost::multi_array into its indices?
在我的程序中,我使用 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 中是否有任何内置有效方法用于指针到索引的转换?或者唯一的可能性是手动划分相对于维度上的数据开始的偏移量?
这个设施好像不在图书馆里。这有点遗憾,但从图书馆的 interface/use 个案例来看是有道理的。
我想出了这个解决方案,它考虑了所有存储 orderings/directions 和基本索引:
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);
}
这会导致按内存顺序为每个元素打印映射坐标:
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}, {0, 1, 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}} Checked a total of 72
elements, verified ok
完整列表
#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);
}
在我的程序中,我使用 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 中是否有任何内置有效方法用于指针到索引的转换?或者唯一的可能性是手动划分相对于维度上的数据开始的偏移量?
这个设施好像不在图书馆里。这有点遗憾,但从图书馆的 interface/use 个案例来看是有道理的。
我想出了这个解决方案,它考虑了所有存储 orderings/directions 和基本索引:
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);
}
这会导致按内存顺序为每个元素打印映射坐标:
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}, {0, 1, 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}} Checked a total of 72 elements, verified ok
完整列表
#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);
}