What about using a view-base approach?
Here I show a solution using ranges::any_view
. At the bottom of this answer is the complete working code, which shows that the vector-like entity we make up is actually pointing to the very elements of the original std::vector
.
Beware, I'm not addressing performance in any way. I don't claim to know much of it, in general, and I know even less of it as related to the cost of the abstractions I'm using below.
The core of the solution is this function for dropping only one element, the one with index i
, form the input range
:
constexpr auto shoot =
[](std::size_t i, auto&& range)
-> any_view<char const&, category::random_access> {
return concat(range | take(i), range | drop(i + 1));
};
In detail,
- given the index
i
of the item to be removed from the input range
,
- it creates a range by
take
ing the first i
elements from range
(these are the elements before the element of index i
),
- it creates a range by
drop
ing the first i + 1
elements from range
(thus retaining the elements after the element of index i
),
- and finally it
concat
enates those two ranges
- returning the resulting range as an
any_view<char const&, category::random_access>
, to avoid nesting more and more views for each repeated application of shoot
;
category::random_access
is what allows a []
-based access to the elements.
Given the above, deleting a few elements from the range is as easy as this:
auto w = shoot(3, shoot(9, shoot(10, v)));
However, if you were to call shoot(9, shoot(3, v))
you would be removing the 3rd element first, and then the 9th element of the resulting range, which means that you'd have removed the 3rd and 10th elements with respect to the original vector; this has nothing to do with the range-base approach, but just with providing a function to delete only one element.
Clearly you can build on top of that a function that eliminates all the indices from another range:
sort
the indices
of elements to be removed,
for
-loop on them in reverse
(for the reason explained above),
- and
shoot
them one by one (without using any_view
we couldn't do view = shoot(n, view);
because each application of shoot
would change the type of the view
):
constexpr auto shoot_many = [](auto&& indices, auto&& range){
any_view<char const&, category::random_access> view{range};
sort(indices);
for (auto const& idx : reverse(indices)) {
view = shoot(idx, view);
}
return view;
};
I have tried another solution for shoot_many
, where I would basically index all the elements of the range
, filter
out those with index contained in indices
, and finally transform
ing to remove the indices. Here's sketch of it:
constexpr auto shoot_many = [](auto&& indices, auto&& range){
std::set<std::size_t> indices_(indices.begin(), indices.end()); // for easier lookup
// (assuming they're not sorted)
auto indexedRange = zip(range, iota(0)); // I pretty sure there's a view doing this already
using RandomAccessViewOfChars
= any_view<char const&, category::random_access>;
return RandomAccessViewOfChars{
indexedRange | filter([indices_](auto&& pair){ return indices_.contains(pair.second); })
| transform([](auto&& pair){ return pair.first; })};
};
This, however, doesn't work because having filter
in the pipe means that we don't know the length of the resulting range until the moment we truly traverse it, which in turn means that the output I'm returning doesn't meet the compile-time requirements for a category::random_access
any_view
. Sad.
Anyway, here's the solution:
#include <assert.h>
#include <cstddef>
#include <functional>
#include <iostream>
#include <memory>
#include <range/v3/algorithm/sort.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/any_view.hpp>
#include <range/v3/view/concat.hpp>
#include <range/v3/view/drop.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/take.hpp>
#include <set>
#include <vector>
using namespace ranges;
using namespace ranges::views;
// utility to drop one element from a range and give you back a view
constexpr auto shoot =
[](std::size_t i, auto&& range)
-> any_view<char const&, category::random_access> {
return concat(range | take(i), range | drop(i + 1));
};
constexpr auto shoot_many = [](auto&& indices, auto&& range){
any_view<char const&, category::random_access> view{range};
sort(indices);
for (auto const& idx : reverse(indices)) {
view = shoot(idx, view);
}
return view;
};
int main() {
// this is the input
std::vector<char> v = iota('a') | take(26) | to_vector;
// alternavively, = {'a', 'b', ...)
// remove a few elements by index
auto w = shoot_many(std::vector<int>{3, 10, 9}, v);
for (std::size_t i = 0, j = 0; i != v.size(); ++i, ++j) {
if (i == 10 || i == 9 || i == 3) {
--j;
std::cout << v[i] << ',' << '-' << std::endl;
} else {
std::cout << v[i] << ',' << w[j] << std::endl;
assert( v[i] == w[j]);
assert(&v[i] == &w[j]);
}
}
}