I'd use standard library algorithms here.
It strikes me that you can use the random_access
iterator with std::stable_partition
to sort all the removables at the end before erasing in bulk:
using Record = std::pair<int, int>;
struct Hasher : std::hash<int> { };
using memberFirst = bmi::member<Record, Record::first_type, &Record::first>;
using memberSecond = bmi::member<Record, Record::second_type, &Record::second>;
using Container = bmi::multi_index_container<
Record, //
bmi::indexed_by< //
bmi::random_access<>, //
bmi::hashed_non_unique<memberFirst, Hasher>, //
bmi::hashed_non_unique<memberSecond, Hasher> //
>>;
I renamed the value type to Record
to avoid confusion with std::pair
void remove_indices(Container& from, std::vector<int> const& removables) {
std::vector<std::reference_wrapper<Record const>> //
arrangement(from.begin(), from.end());
auto pivot = std::stable_partition(
arrangement.begin(), arrangement.end(), [&](Record const& rec) {
auto index = from.iterator_to(rec) - from.begin();
return (end(removables) ==
std::find(removables.begin(), removables.end(), index));
});
from.rearrange(begin(arrangement));
auto n_remaining = pivot - arrangement.begin();
from.erase(from.begin() + n_remaining, from.end());
}
Demo Live On Compiler Explorer:
Before [(1, 11), (2, 22), (3, 33), (4, 44), (5, 55), (6, 66), (7, 77), (8, 88), (9, 99)]
After [(1, 11), (2, 22), (4, 44), (5, 55), (7, 77), (9, 99)]
Simpler And More Efficient?
It strikes me that the complexity largely vanishes if you know that:
- indexes are a fixed order
- not duplicated
So, let's use that idea as well:
void remove_indices(Container& from, std::set<int, std::greater<> > const& descending) {
for (auto& index : descending)
from.erase(from.iterator_to(from[index]));
}
This trades efficiencies and complexities. Use taste and profilers to decide which version to use. See it live again:
Live On Compiler Explorer
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index_container.hpp>
namespace bmi = boost::multi_index;
using Record = std::pair<int, int>;
struct Hasher : std::hash<int> { };
using memberFirst = bmi::member<Record, Record::first_type, &Record::first>;
using memberSecond = bmi::member<Record, Record::second_type, &Record::second>;
using Container = bmi::multi_index_container<
Record, //
bmi::indexed_by< //
bmi::random_access<>, //
bmi::hashed_non_unique<memberFirst, Hasher>, //
bmi::hashed_non_unique<memberSecond, Hasher> //
>>;
void remove_indices(Container& from, std::set<int, std::greater<> > const& descending) {
for (auto& index : descending)
from.erase(from.iterator_to(from[index]));
}
#include <fmt/ranges.h>
int main() {
Container container{
{1, 11}, {2, 22}, {3, 33}, {4, 44}, {5, 55}, {6, 66}, {7, 77},
{8, 88}, {9, 99},
};
fmt::print("Before {}\n", container);
remove_indices(container, {2,5,7});
fmt::print("After {}\n", container);
}
Prints
Before {(1, 11), (2, 22), (3, 33), (4, 44), (5, 55), (6, 66), (7, 77), (8, 88), (9, 99)}
After {(1, 11), (2, 22), (4, 44), (5, 55), (7, 77), (9, 99)}
Side Note
One of the main advantages of MultiIndex containers is that you don't have to settle for non-descript types like std::pair
with nondescript names as first
or second_type
. Brrr. Much nicer:
Live On Compiler Explorer
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index_container.hpp>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#include <iomanip>
namespace bmi = boost::multi_index;
struct Record {
int age;
std::string name;
friend std::ostream& operator<<(std::ostream& os, Record const& r) {
return os << "(" << std::quoted(r.name) << "," << r.age << ")";
}
};
using Container = bmi::multi_index_container<
Record,
bmi::indexed_by<
bmi::random_access<>,
bmi::hashed_non_unique<bmi::member<Record, std::string, &Record::name>>,
bmi::hashed_non_unique<bmi::member<Record, int, &Record::age>>
>>;
void remove_indices(Container& from,
std::set<int, std::greater<>> const& descending)
{
for (auto& index : descending)
from.erase(from.iterator_to(from[index]));
}
int main() {
Container container{
{1, "Alyssa"}, {2, "Baya"}, {3, "Chom"}, {4, "Dirk"}, {5, "Evie"},
{6, "Ferd"}, {7, "Gerald"}, {8, "Homer"}, {9, "Isala"},
};
fmt::print("Before {}\n", container);
remove_indices(container, {7, 2, 5, 7});
fmt::print("After {}\n", container);
}
Prints
Before {("Alyssa",1), ("Baya",2), ("Chom",3), ("Dirk",4), ("Evie",5), ("Ferd",6), ("Gerald",7), ("Homer",8), ("Isala",9)}
After {("Alyssa",1), ("Baya",2), ("Dirk",4), ("Evie",5), ("Gerald",7), ("Isala",9)}