C++ 当从std::(无序)集合中删除项目时,如何有效地迭代该集合?
我有一堆元素存储在某个容器中。他们的命令对我来说无关紧要 我迭代我的容器,并为每个元素检查一些谓词-p。如果P为true-从容器中删除元素。如果P为false-只需转到下一个。 如果在迭代过程中至少删除了一个元素,我将重复这个过程。在新的迭代中,对于在先前迭代中为false的元素,P有可能为true 我已经为此编写了一个代码C++ 当从std::(无序)集合中删除项目时,如何有效地迭代该集合?,c++,set,C++,Set,我有一堆元素存储在某个容器中。他们的命令对我来说无关紧要 我迭代我的容器,并为每个元素检查一些谓词-p。如果P为true-从容器中删除元素。如果P为false-只需转到下一个。 如果在迭代过程中至少删除了一个元素,我将重复这个过程。在新的迭代中,对于在先前迭代中为false的元素,P有可能为true 我已经为此编写了一个代码 std::unordered_map<T, T> container; auto it = container.begin(); while (it != co
std::unordered_map<T, T> container;
auto it = container.begin();
while (it != container.end()) {
if (predicate(*it)) {
it = container.erase(it);
} else {
it++;
}
}
我有一个问题:
考虑到我的容器中大约有500个元素,从干净的代码和时间效率两方面来看,有没有更好的方法来实现这一点。您希望在容器上循环迭代,直到完成一个完整的过程,并且不删除任何内容
template<class C, class F>
void multi_pass_erase( C& c, F&& f )
{
auto stop_at = c.end();
auto it = current;
while (true)
{
if (c.empty())
return;
if (f(*it))
{
it = c.erase(it);
if (it == c.begin())
stop_at = c.end();
else
stop_at = it;
}
else
{
++it;
if (it == stop_at)
return;
}
if (it == c.end())
it = c.begin();
}
}
想象一下,如果每个循环删除一个元素。这可能需要^2次
在这种情况下,multi_pass_擦除的效果并没有惊人地好。如果每个元素都导致前一个元素被擦除,则multi_pass_擦除不会减少任何访问;在这两种情况下,在找到下一个要删除的节点之前,必须访问每个未删除的节点
基本上,当至少有一次擦除时,所有对multi_pass_擦除的幻想都会使set每次调用的平均迭代减少一半,就好像我们假设最后一次擦除是随机定位的一样,我们跳过了对容器一半的平均操作
增加的复杂性可能不值得
但我们能写出更复杂、更有效的东西吗
通常,当您删除可能导致其他内容需要删除的内容时,您可以获得有关这些其他内容的信息
考虑跟踪这些信息,只查看这些元素,而不是再次查看整个列表
template<class C, class Test, class Dependencies>
void dependent_erase( C& c, Test&& t, Dependencies&& d ) {
auto it = c.begin();
using key_type = typename C::key_type;
std::vector<key_type> todo_list;
while (it != c.end())
{
if (t(*it)) {
d( *it, &todo_list );
it = c.erase(it);
} else {
++it;
}
}
// remove duplicates:
std::vector<key_type> next_todo_list;
while (!todo_list.empty()) {
// better to shrink the list and ask f(x) less often
std::sort(todo_list.begin(), todo_list.end());
todo_list.erase( std::unique(todo_list.begin(), todo_list.end()), todo_list.end() );
for (auto&& todo : todo_list) {
auto it = c.find( todo );
if (f(*it))
{
d( *it, &next_todo_list );
c.erase(it);
}
}
todo_list = std::move( next_todo_list );
next_todo_list.clear();
}
}
这里有我们的测试t我们想删除此项目吗?如果我们这样做,我们调用d item,vector*,并存储我们想要在那里重新测试的任何直接依赖项
然后我们检查容器,根据需要移除物品。然后,我们检查依赖项并删除任何提到的应该删除的内容,重复执行,直到我们不再找到要删除的新项目
如果我们假设您的代码是一组引用了其他节点的节点,并且您正在进行垃圾收集,那么在许多情况下,这应该要好得多
未测试甚至未编译的代码。但我以前做过,所以可能有用。它至少应该作为伪代码工作
如果您希望每个删除的节点都有很多依赖项,并且有很多重叠,那么基于集合的todo列表可能比向量列表更好。也就是说,如果删除了N个元素,每个元素都有M个依赖项,那么该向量将增长到NM大小。但是,如果M往往很小,并且与其他元素没有太多重叠,那么向量将比基于节点的集合快得多。在循环中使用:
while (std::erase_if(your_set, your_predcate))
/**/;
如果你没有C++20,不要绝望。Cppreference.com也给出了一个示例实现
如果它被证明是一个瓶颈,那么手动滚动您自己的all_erase_If,并对基于节点的容器进行专门化可能会很有用:
template <class T>
constexpr bool has_node_type = requires { typename T::node_type; };
template <class T>
constexpr bool is_node_based = has_node_type<T>;
template <class C, class P>
auto all_erase_if(C& c, F f) requires is_node_based<C> {
const auto old_size = std::size(c);
if (!old_size)
return old_size;
auto it = std::begin(c), stop = std::begin(c);
do {
while (f(*it)) {
it = stop = c.erase(it);
if (it != std::end(c))
/**/;
else if (std::empty(c))
return old_size;
else
it = stop = std::begin(c);
}
if (++it == std::end(c))
it = std::begin(c);
} while (it != stop);
return old_size - std::size(c);
}
template <class C, class P>
auto all_erase_if(C& c, F f) requires !is_node_based<C> {
const auto old_size = std::size(c);
while (std::erase_if(c, std::ref(f)))
/**/;
return old_size - std::size(c);
}
请不要在您的问题中添加问题列表,它将因缺乏焦点而关闭。请尝试一次只问一个问题。您共享的代码与您的描述不匹配。当元素被删除时,它不会重复该过程。您正在检查*掩码上的谓词,这与用于在容器上循环的迭代器无关,从您共享的不可编译代码可以看出。简言之,您提供的代码与描述几乎没有关系。*mask还是*it?因此它实际上是一个任意容器,或者它是专门映射/无序的_映射?考虑到向量或DEGO,但不适用于map / unDead映射,有一些可能性。
template <class T>
constexpr bool has_node_type = requires { typename T::node_type; };
template <class T>
constexpr bool is_node_based = has_node_type<T>;
template <class C, class P>
auto all_erase_if(C& c, F f) requires is_node_based<C> {
const auto old_size = std::size(c);
if (!old_size)
return old_size;
auto it = std::begin(c), stop = std::begin(c);
do {
while (f(*it)) {
it = stop = c.erase(it);
if (it != std::end(c))
/**/;
else if (std::empty(c))
return old_size;
else
it = stop = std::begin(c);
}
if (++it == std::end(c))
it = std::begin(c);
} while (it != stop);
return old_size - std::size(c);
}
template <class C, class P>
auto all_erase_if(C& c, F f) requires !is_node_based<C> {
const auto old_size = std::size(c);
while (std::erase_if(c, std::ref(f)))
/**/;
return old_size - std::size(c);
}