Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Most efficient way to erase from a set while iterating over it

Tags:

c++

What's the most efficient way to erase from a set while iterating over it? Here are two approaches I've thought of, which is the best between them? Is there another better way?

void WaitForFiles(std::set<string> files) {
  while (files.size() > 0) {
    std::set<string> found_files;
    for (const auto& file : files) {
      if (Exists(file)) {
        found_files.insert(file);
      }
    }
    for (const auto& found_file : found_files) {
      files.erase(file);
    }
  }
}

Using set_difference:

void WaitForFiles(std::set<string> files) {
  while (files.size() > 0) {
    std::set<string> found_files;
    for (const auto& file : files) {
      if (Exists(file)) {
        found_files.insert(file);
      }
    }
    std::set<string> difference;
    std::set_difference(files.begin(), files.end(),
                        found_files.begin(), found_files.end(),
                        std::inserter(difference, difference.end()));
    files = difference;
  }
}

Note that the following crashes:

void WaitForFiles(std::set<string> files) {
  while (files.size() > 0) {
    for (const auto& file : files) {  // <-- seg fault after first erase
      if (Exists(file)) {
        files.erase(file);
      }
    }
  }
}

For determining efficiency, keep in mind that in my case the file may take 30 minutes to come into existence, so the Exists function will get called many times and the files set will not change very often compared to the number of times the loop is iterated over.

like image 665
AffluentOwl Avatar asked Sep 04 '15 21:09

AffluentOwl


People also ask

How will you efficiently remove elements while iterating a collection?

The right way to remove objects from ArrayList while iterating over it is by using the Iterator's remove() method. When you use iterator's remove() method, ConcurrentModfiicationException is not thrown.

How do you remove while iterating?

An element can be removed from a Collection using the Iterator method remove(). This method removes the current element in the Collection. If the remove() method is not preceded by the next() method, then the exception IllegalStateException is thrown.

How do you remove an element from a set using iterator?

Erasing elements from std::set while Iterating std::set provides a member function erase() that accepts an iterator as an argument and deletes it from the set i.e. iterator erase (const_iterator position); iterator erase (const_iterator position); iterator erase (const_iterator position);


2 Answers

It is undefined behaviour to erase from a set within a ranged-based for loop (even if it appears to work). Ranged-based for loops use an iterator internally and erasing the element invalidates the iterator.

But std::set::erase returns a valid iterator to the next element in the std::set so you can use an explicit iterator loop instead:

for(auto itr = files.cbegin(); itr != files.cend();) {
  if (exists(*itr)) {
    std::cout << "Found file: " << *itr << "\n";
    itr = files.erase(itr);
  } else
    ++itr;
}

Live demo.

like image 132
Chris Drew Avatar answered Oct 13 '22 01:10

Chris Drew


With std::experimental::erase_if from v2 of the library fundamentals TS:

std::experimental::erase_if(files, Exists);

If Exists is overloaded or is a function template, use a lambda:

std::experimental::erase_if(files, [](const auto& f) { return Exists(f); });

This is already implemented in VS2015.

like image 28
T.C. Avatar answered Oct 13 '22 00:10

T.C.