Removing elements marked for removal with Ranges-V3
Asked Answered
N

1

6

I've got two vectors:

struct MyData{
     double value; 
};
std::vector<int> remove_flags = {0, 1, 0, 0, 0, 0, 1, 0};
std::vector<MyData> data =      {{},{},{},{},{},{},{},{}}; 

The remove_flags vector contains an array of flags of the exact same size as data, each flag is either 0, or 1, where 1 means the data should be removed.

I would like to use remove_flags to remove elements from data in place, ie performing the erase remove idiom, but erasing based on values in remove_flags. The end result should be data with elements erased, and hopefully remove_flags with those same elements erased.

Doing this manually is annoying, and I wanted to use Range-v3 for this. I'm currently using C++17.

After looking through the documentation, I don't think I've found a solution, the closest thing I could come up with is:

auto result = ranges::views::zip(remove_flags, data) | ranges::actions::remove_if([](std::pair<const int&, const MyData&> pair){
    return pair.first != 0;
});

remove_flags.erase(result.first, remove_flags.end());
data.erase(result.second, data.end());

But actions cannot operate on the view zip, so this does not compile. If I switch ranges::actions::remove_if to ranges::views::remove_if a strange views object is returned, presumably one that has not actually performed the std::remove equivalent operation on the two vectors.

I could use contaner_to but that would involve a copy, and I don't want to pay that kind of unnecessary penalty for convenience. I've seen what I want accomplished in boost where actual zip pair iterators can be used to return two separate removal results.

Is this kind of pattern possible in Range-v3?

Nannettenanni answered 3/6, 2021 at 20:30 Comment(10)
Is there a reason why you actually want a std::vector of the results? Isn't a view enough?Pyrogenous
@Pyrogenous Please clarify your question, no where in my post do I mention or demonstrate a "standard vector of result". In fact, it's impossible for my example to show this, since I say result.first and result.secondNannettenanni
The last two lines of your snippet show that you alter both data and remove_flags, so I think it's fair to presume you want to use those afterward, i.e. those two std::vectors contain the "output/result/effect" you want to achieve. That's where you "mention" in your post that you want the result of the code to be in a std::vector (result, not result, just as in my previous comment). However, let's put it in another way: your example is not a minimal reproducible example; can you either make it one or describe in words what the desired outcome/result/effect of the code is?Pyrogenous
@Pyrogenous the above example now represents an MVCE, and I've clarified what I want. Note, the title was not separate from my post, and that the boost example (first part) gives effectively what I want exactly, just with out range syntax.Nannettenanni
Thanks for clarifying. I've deleted my answer for now, because it's clearly not a good answer at the moment. I don't have any idea at the moment. I'd be curious to know why you need to remove the elments in place, and having a view on them is not enough.Pyrogenous
@Pyrogenous In real code, MyData represents Dear ImGUI table row items. Because ImGUI is immediate mode, in order to remove elements from the UI, I must first mark elements some how (ie selection) then remove them afterwards, as displaying rows is literally a for loop that runs each time. Again, it's UI is immediate mode, selection doesn't persist with out me creating an array that maintains the selection (effectively remove_flags). Removing these elements removes the UI rows. These rows also represent markers, removing the MyData then also removes the markers (which is desired).Nannettenanni
Ok, thanks for clarifying this. I have just one misunderstanding left: how is the loop done? If it was done via for (auto x : result) or for (auto it = result.begin(); it != result.end(); ++it), then a view would be fine.Pyrogenous
@Pyrogenous So the loop effectively looks something like (in half psuedo code) if(ImGui::BeginTable()){ ...; clipped.Begin(data.size()); for(int row_n = clipped.Start; row_n < clipped.End; ++i){ auto& my_data = data[row_n]; if(ImGui::Selectable("",remove_flags[row_n],...){...};if(ImGui::DisplayColumn(0)){ ImGui::InputText("input", &(my_data.name))}; if(ImGui::DisplayColumn(1)){...}}ImGui::EndTable();} The loop is not trivial, I need references to mydata inside the loop itself, I use the values of my_data inside the loop itself, need to modify it from within the generated UI.Nannettenanni
@Pyrogenous if this code looks weird, that's because this his how ImGUI is architected, see here: github.com/ocornut/imgui/blob/…Nannettenanni
Oww... to far from my way of coding.Pyrogenous
O
6

Eager operation on existing ranges is the domain of algorithms. The only slightly tricky bit here is recovering iterators into the vectors since zip doesn't give you a direct way to get those. So instead recover them using the distance between the new end and the start of the range:

auto z = ranges::views::zip(remove_flags, data);
auto e = ranges::remove_if(z, [](auto&& r){ return r.first; });
data.erase(data.begin() + (e - z.begin()), data.end());
remove_flags.erase(remove_flags.begin() + (e - z.begin()), remove_flags.end());

Demo.

Officiary answered 5/6, 2021 at 2:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.