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.
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.
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.
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);
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With