Astor
Astor

Reputation: 394

C++23 (or later) succinct way to extract elements of an array using an index vector?

I've been reviewing new ways of slicing arrays using the latest standards, but it's been a bit overwhelming and I have a question.

Is there any new, succinct way, with the new C++23 additions to ranges, views, mdspan, or even the ones to come submdspan, mdarray, to extract elements of an mdspan into another container using a vector with the indices to be extracted?

I have to work with std::array that I know their size beforehand, however, the indices will be known only at runtime so I can use temporary std::vector types or other containers.

Here's a godbolt example, reproduced below

#include <iostream>
#include <array>
#include <vector>
#include <https://raw.githubusercontent.com/kokkos/mdspan/single-header/mdspan.hpp>

int main()
{
    constexpr std::size_t pDeg = 16 ;
    std::array<std::size_t,pDeg*pDeg> arr;
    for (std::size_t i=0;i<arr.size();i++)
        arr[i] = i ;

    auto md = std::mdspan<std::size_t,std::extents<std::size_t,pDeg,pDeg>>(arr.data());

    std::vector<std::size_t> ind1 = {1,5,6,7,8,9,4,5};
    std::vector<std::size_t> ind2 = {14,11,12,13,11,1,5,6};

}

I have functions implemented to operate on matrices: multiply(), add(), etc

I would like to write matrix algebra expressions like

auto result = add(multiply(md[ind1,:],md[:,ind2]),md[ind1,ind2]);

where md[ind1,:] is a submatrix containing rows 1,5,6,7,8,9,4,5 of md, md[:,ind2] contains columns 14,11,12,13,11,1,5,6 of md and md[ind1,ind2] is a 8-by-8 submatrix of md containing all combinations of rows and columns given by ind1 and ind2 respectively (notice that indices can be repeated).

or something like

auto result = add(multiply(md.extract<0>(ind1),md.extract<1>(ind2)),md.extract<0,1>(ind1,ind2));

where result is always a std::array that I know the size at compile time.

The methods do not necessarily have to be the ones I propose, but they needs to be in a sort of short notation since there will be many matrix algebra operations.

Upvotes: 1

Views: 335

Answers (1)

alfC
alfC

Reputation: 16300

I believe the most succinct way to select rows or columns is to use the following transformation. This is not necessarily the most efficient code, but it is quite general.

template<class MDSpanArray, class Selection1, class Selection2>
auto select(MDSpanArray&& arr, Selection1 ind1, Selection2 ind2) {
    return std::views::transform(
        std::move(ind1),
        [ind2 = std::move(ind2), &arr](auto i) {
            return std::views::transform(ind2, [i, &arr](auto j) {return arr[i, j];});
        }
    );
}

you can use it like this, the selections can be concrete dynamic arrays or a iota range based on the sizes:

    auto&& arr2_identity = select(arr2, std::views::iota(0UL, arr2.extent(0)), std::views::iota(0UL, arr2.extent(1)));

    auto&& arr2_rows_ind1 = select(arr2, ind1, std::views::iota(0UL, arr2.extent(1)));
    auto&& arr2_cols_ind2 = select(arr2, std::views::iota(0UL, arr2.extent(0)), ind2);

    auto&& arr2_ind1_ind2 = select(arr2, ind1, ind2);

Here it is the full code, https://godbolt.org/z/aodo8dsvs.

I don't understand what you mean by the add and multiply functions, but it seems that you have a good idea on how to implement those.


The overall code resulted a bit unergonomic, mainly because the original array (mdspan) and the selection would have slightly different access syntax. So I gave it a shot with my own library (https://github.com/correaa/boost-multi) as well.

In this case you can use it like this:

https://godbolt.org/z/sEG5YvfYq

    multi::array<std::size_t, 2> arr2 = ...;
    ...

    using multi::_;

    select(arr2, _   , _   );  // or select(arr2);

    select(arr2, ind1, _   );  // or select(arr2, ind1);
    select(arr2, _   , ind2);

    select(arr2, ind1, ind2);

The functions can be further optimized in each case, but that requires a somewhat advanced use of library features: https://godbolt.org/z/aa1cbY6bf

Upvotes: 0

Related Questions