C++ 当从std::(无序)集合中删除项目时,如何有效地迭代该集合?

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

我有一堆元素存储在某个容器中。他们的命令对我来说无关紧要

我迭代我的容器,并为每个元素检查一些谓词-p。如果P为true-从容器中删除元素。如果P为false-只需转到下一个。 如果在迭代过程中至少删除了一个元素,我将重复这个过程。在新的迭代中,对于在先前迭代中为false的元素,P有可能为true

我已经为此编写了一个代码

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);
}