Suppose that I have a vector of something:
std::vector<Foo> v;
This vector is sorted, so equal elements are next to each other.
What is the best way to get all iterator pairs representing ranges with equal elements (using the standard library)?
while (v-is-not-processed) {
iterator b = <begin-of-next-range-of-equal-elements>;
iterator e = <end-of-next-range-of-equal-elements>;
for (iterator i=b; i!=e; ++i) {
// Do something with i
}
}
I'd like to know how to get values of b and e in the code above.
So, for example, if v contains these numbers:
index 0 1 2 3 4 5 6 7 8 9
value 2 2 2 4 6 6 7 7 7 8
Then I'd like to have b and e point to elements in the loop:
iteration b e
1st 0 3
2nd 3 4
3rd 4 6
4th 6 9
5th 9 10
Is there an elegant way to solve this with the standard library?
This is basically Range v3's group_by: group_by(v, std::equal_to{}). It doesn't exist in the C++17 standard library, but we can write our own rough equivalent:
template <typename FwdIter, typename BinaryPred, typename ForEach>
void for_each_equal_range(FwdIter first, FwdIter last, BinaryPred is_equal, ForEach f) {
while (first != last) {
auto next_unequal = std::find_if_not(std::next(first), last,
[&] (auto const& element) { return is_equal(*first, element); });
f(first, next_unequal);
first = next_unequal;
}
}
Usage:
for_each_equal_range(v.begin(), v.end(), std::equal_to{}, [&] (auto first, auto last) {
for (; first != last; ++first) {
// Do something with each element.
}
});
You can use std::upper_bound to get the iterator to the "next" value. Since std::upper_bound returns an iterator to the first element greater than that value provided, if you provide the value of the current element, it will give you an iterator that will be one past the end of the current value. That would give you a loop like
iterator it = v.begin();
while (it != v.end()) {
iterator b = it;
iterator e = std::upper_bound(it, v.end(), *it);
for (iterator i=b; i!=e; ++i) {
// do something with i
}
it = e; // need this so the loop starts on the next value
}
You are looking for std::equal_range.
Returns a range containing all elements equivalent to value in the
range [first, last).
Something like the following should work.
auto it = v.begin();
while (it != v.end())
{
auto [b, e] = std::equal_range(it, v.end(), *it);
for (; b != e; ++b) { /* do something in the range[b, e) */ }
it = e; // need for the beginning of next std::equal_range
}
Remark: Even though this will be an intuitive approach, the std::equal_range obtains its first and second iterators(i.e b and e) with the help of std::lower_bound and std::upper_bound, which makes this approche slightly inefficient. Since, the first iterator could be easily accessible for the OP's case, calling std::upper_bound for second iterator only neccesarry(as shown by #NathanOliver 's answer).
If your ranges of equal values is short, then std::adjacent_find would work well:
for (auto it = v.begin(); it != v.end();) {
auto next = std::adjacent_find(it, v.end(), std::not_equal_to<Foo>());
for(; it != next; ++it) {
}
}
You can also substitute a lambda for std::not_equal_to if you wish.
But even if we don't use e for anything, this formulation is convenient, it's harder to make an error. The other way (to check for changing values) is more tedious (as we need to handle the last range specially [...])
Depends on how you interpret 'handling last range specially':
auto begin = v.begin();
// we might need some initialization for whatever on *begin...
for(Iterator i = begin + 1; ; ++i)
{
if(i == v.end() || *i != *begin)
{
// handle range single element of range [begin, ???);
if(i == v.end())
break;
begin = i;
// re-initialize next range
}
}
No special handling for last range – solely, possibly needing the initialization code twice...
Nested-loop-approach:
auto begin = v.begin();
for(;;)
{
// initialize first/next range using *begin
for(Iterator i = begin + 1; ; ++i)
{
if(i == v.end() || *i != *begin)
{
// handle range single element of range [begin, ???);
if(i == v.end())
goto LOOP_EXIT;
begin = i;
break;
}
}
}
LOOP_EXIT:
// go on
// if nothing left to do in function, we might prefer returning over going to...
More elegant? Admitted, I'm in doubt myself... Both approaches avoid iterating over the same range twice (first for finding the end, then the actual iteration), though. And if we make our own library function from:
template <typename Iterator, typename RangeInitializer, typename ElementHandler>
void iterateOverEqualRanges
(
Iterator begin, Iterator end,
RangeInitializer ri, ElementHandler eh
)
{
// the one of the two approaches you like better
// or your own variation of...
}
we could then use it like:
std::vector<...> v;
iterateOverEqualRanges
(
v.begin(), v.end(),
[] (auto begin) { /* ... */ },
[] (auto current) { /* ... */ }
);
Now finally, it looks similiar to e. g. std::for_each, doesn't it?
for(auto b=v.begin(), i=b, e=v.end(); i!=e; b=i) {
// initialise the 'Do something' code for another range
for(; i!=e && *i==*b; ++i) {
// Do something with i
}
}
Related
I have this code:
int main()
{
vector<int> res;
res.push_back(1);
vector<int>::iterator it = res.begin();
for( ; it != res.end(); it++)
{
it = res.erase(it);
//if(it == res.end())
// return 0;
}
}
"A random access iterator pointing to the new location of the element that followed the last element erased by the function call, which is the vector end if the operation erased the last element in the sequence."
This code crashes, but if I use the if(it == res.end()) portion and then return, it works. How come? Does the for loop cache the res.end() so the not equal operator fails?
res.erase(it) always returns the next valid iterator, if you erase the last element it will point to .end()
At the end of the loop ++it is always called, so you increment .end() which is not allowed.
Simply checking for .end() still leaves a bug though, as you always skip an element on every iteration (it gets 'incremented' by the return from .erase(), and then again by the loop)
You probably want something like:
while (it != res.end()) {
it = res.erase(it);
}
to erase each element
(for completeness: I assume this is a simplified example, if you simply want every element gone without having to perform an operation on it (e.g. delete) you should simply call res.clear())
When you only conditionally erase elements, you probably want something like
for ( ; it != res.end(); ) {
if (condition) {
it = res.erase(it);
} else {
++it;
}
}
for( ; it != res.end();)
{
it = res.erase(it);
}
or, more general:
for( ; it != res.end();)
{
if (smth)
it = res.erase(it);
else
++it;
}
Because the method erase in vector return the next iterator of the passed iterator.
I will give example of how to remove element in vector when iterating.
void test_del_vector(){
std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
//method 1
for(auto it = vecInt.begin();it != vecInt.end();){
if(*it % 2){// remove all the odds
it = vecInt.erase(it); // note it will = next(it) after erase
} else{
++it;
}
}
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
// recreate vecInt, and use method 2
vecInt = {0, 1, 2, 3, 4, 5};
//method 2
for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
if (*it % 2){
it = vecInt.erase(it);
}else{
++it;
}
}
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
// recreate vecInt, and use method 3
vecInt = {0, 1, 2, 3, 4, 5};
//method 3
vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
[](const int a){return a % 2;}),
vecInt.end());
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
}
output aw below:
024
024
024
A more generate method:
template<class Container, class F>
void erase_where(Container& c, F&& f)
{
c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
c.end());
}
void test_del_vector(){
std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
//method 4
auto is_odd = [](int x){return x % 2;};
erase_where(vecInt, is_odd);
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
}
Something that you can do with modern C++ is using "std::remove_if" and lambda expression;
This code will remove "3" of the vector
vector<int> vec {1,2,3,4,5,6};
vec.erase(std::remove_if(begin(vec),end(vec),[](int elem){return (elem == 3);}), end(vec));
The it++ instruction is done at the end of the block. So if your are erasing the last element, then you try to increment the iterator that is pointing to an empty collection.
Do not erase and then increment the iterator. No need to increment, if your vector has an odd (or even, I don't know) number of elements you will miss the end of the vector.
You increment it past the end of the (empty) container in the for loop's loop expression.
The following also seems to work :
for (vector<int>::iterator it = res.begin(); it != res.end(); it++)
{
res.erase(it--);
}
Not sure if there's any flaw in this ?
if(allPlayers.empty() == false) {
for(int i = allPlayers.size() - 1; i >= 0; i--)
{
if(allPlayers.at(i).getpMoney() <= 0)
allPlayers.erase(allPlayers.at(i));
}
}
This works for me. And Don't need to think about indexes have already erased.
As a modification to crazylammer's answer, I often use:
your_vector_type::iterator it;
for( it = res.start(); it != res.end();)
{
your_vector_type::iterator curr = it++;
if (something)
res.erase(curr);
}
The advantage of this is that you don't have to worry about forgetting to increment your iterator, making it less bug prone when you have complex logic. Inside the loop, curr will never be equal to res.end(), and it will be at the next element regardless of if you erase it from your vector.
The function removeAll(vector<int>& v, const int& x) intends to remove all elements that equals to int x except the first one.
For example
originally
v = [2,2,3,5,5,6,2,8,6]
after removeAll(v, 2)
the output should be [2,3,5,5,6,8,6]
But my code seems fail to compare if (*it == x), so does anyone know the reason? I didn't find any similar question online.
void removeAll(vector<int>& v, const int& x)
{
for (vector<int>::iterator it = v.begin(); it < v.end(); it++)
{
int count = 0;
if (*it == x && count > 0)
{
v.erase(it);
}
if (*it == x)
{
count++;
}
}
}
What you want is this:
void remove_duplicates_of(std::vector<int>& v, int value)
{
auto it = std::find(v.begin(), v.end(), value);
if (it != v.end())
v.erase(std::remove(std::next(it), v.end(), value), v.end());
}
This finds the first instance of value, and if found, erases every subsequent instance starting at the iterator position immediately past the point of discovery.
There are several problems:
count must be declared outside of the loop, otherwise it's reset to 0 on every iteration.
it must be incremented only when you chose to not erase an element.
When you do erase an element, the return value of erase should be assigned to it. (Failing to do so would formally cause UB, but in practice might not break anything, if you're using std::vector. It's not going to work with for most other containers though.)
Here's the fixed code:
void removeAll(vector<int>& v, const int& x)
{
int count = 0;
for (vector<int>::iterator it = v.begin(); it < v.end();)
{
if (*it == x)
{
count++;
}
if (*it == x && count > 1)
{
it = v.erase(it);
}
else
{
it++;
}
}
}
And here's the same code with minor style improvements:
void removeAll(std::vector<int> &v, int x)
{
std::size_t count = 0;
for (auto it = v.begin(); it < v.end();)
{
if (*it == x && count++ != 0)
it = v.erase(it);
else
it++;
}
}
When you erase an iterator, it gets invalidated. The best way of doing this is with standard algorithms as shown in another answer. The thing is, removing elements from the middle of a vector is expensive. It shifts all the elements after that position by 1 element every time you call it. It's better to shift all the elements in a single iteration and then remove what remains at the end. Removing elements from the end is relatively cheap. Here's roughly how you can do it without standard algorithms:
void remove_duplicates_of(std::vector<int>& v, int x) {
auto it = v.begin();
auto last = v.end();
while (it != last && *it != x) ++it; // find the first one
if (it == last) return; // if we didn't find anything, return
++it; // skip the first
while (it != last && *it != x) ++it; // find the second
if (it == last) return; // if we didn't find anything, return
auto next = it;
while (++it != last) // shift all other elements to front
if (*it != x) *next++ = *it;
// remove the rest
v.erase(next, last);
}
I have this code:
int main()
{
vector<int> res;
res.push_back(1);
vector<int>::iterator it = res.begin();
for( ; it != res.end(); it++)
{
it = res.erase(it);
//if(it == res.end())
// return 0;
}
}
"A random access iterator pointing to the new location of the element that followed the last element erased by the function call, which is the vector end if the operation erased the last element in the sequence."
This code crashes, but if I use the if(it == res.end()) portion and then return, it works. How come? Does the for loop cache the res.end() so the not equal operator fails?
res.erase(it) always returns the next valid iterator, if you erase the last element it will point to .end()
At the end of the loop ++it is always called, so you increment .end() which is not allowed.
Simply checking for .end() still leaves a bug though, as you always skip an element on every iteration (it gets 'incremented' by the return from .erase(), and then again by the loop)
You probably want something like:
while (it != res.end()) {
it = res.erase(it);
}
to erase each element
(for completeness: I assume this is a simplified example, if you simply want every element gone without having to perform an operation on it (e.g. delete) you should simply call res.clear())
When you only conditionally erase elements, you probably want something like
for ( ; it != res.end(); ) {
if (condition) {
it = res.erase(it);
} else {
++it;
}
}
for( ; it != res.end();)
{
it = res.erase(it);
}
or, more general:
for( ; it != res.end();)
{
if (smth)
it = res.erase(it);
else
++it;
}
Because the method erase in vector return the next iterator of the passed iterator.
I will give example of how to remove element in vector when iterating.
void test_del_vector(){
std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
//method 1
for(auto it = vecInt.begin();it != vecInt.end();){
if(*it % 2){// remove all the odds
it = vecInt.erase(it); // note it will = next(it) after erase
} else{
++it;
}
}
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
// recreate vecInt, and use method 2
vecInt = {0, 1, 2, 3, 4, 5};
//method 2
for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
if (*it % 2){
it = vecInt.erase(it);
}else{
++it;
}
}
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
// recreate vecInt, and use method 3
vecInt = {0, 1, 2, 3, 4, 5};
//method 3
vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
[](const int a){return a % 2;}),
vecInt.end());
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
}
output aw below:
024
024
024
A more generate method:
template<class Container, class F>
void erase_where(Container& c, F&& f)
{
c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
c.end());
}
void test_del_vector(){
std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
//method 4
auto is_odd = [](int x){return x % 2;};
erase_where(vecInt, is_odd);
// output all the remaining elements
for(auto const& it:vecInt)std::cout<<it;
std::cout<<std::endl;
}
Something that you can do with modern C++ is using "std::remove_if" and lambda expression;
This code will remove "3" of the vector
vector<int> vec {1,2,3,4,5,6};
vec.erase(std::remove_if(begin(vec),end(vec),[](int elem){return (elem == 3);}), end(vec));
The it++ instruction is done at the end of the block. So if your are erasing the last element, then you try to increment the iterator that is pointing to an empty collection.
Do not erase and then increment the iterator. No need to increment, if your vector has an odd (or even, I don't know) number of elements you will miss the end of the vector.
You increment it past the end of the (empty) container in the for loop's loop expression.
The following also seems to work :
for (vector<int>::iterator it = res.begin(); it != res.end(); it++)
{
res.erase(it--);
}
Not sure if there's any flaw in this ?
if(allPlayers.empty() == false) {
for(int i = allPlayers.size() - 1; i >= 0; i--)
{
if(allPlayers.at(i).getpMoney() <= 0)
allPlayers.erase(allPlayers.at(i));
}
}
This works for me. And Don't need to think about indexes have already erased.
As a modification to crazylammer's answer, I often use:
your_vector_type::iterator it;
for( it = res.start(); it != res.end();)
{
your_vector_type::iterator curr = it++;
if (something)
res.erase(curr);
}
The advantage of this is that you don't have to worry about forgetting to increment your iterator, making it less bug prone when you have complex logic. Inside the loop, curr will never be equal to res.end(), and it will be at the next element regardless of if you erase it from your vector.
This question already has answers here:
Removing item from vector while iterating?
(8 answers)
Closed 2 years ago.
I'm writing this program why it throws an error in toupper('a')?
void test2(void) {
string n;
vector<string> v;
auto it = v.begin();
do {
cout << "Enter a name of a fruit: ";
cin >> n;
v.push_back(n);
} while (n != "Quit");
v.erase(v.end() - 1);
sort(v.begin(), v.end(), [](string g, string l) { return g < l; });
dis(v);
for (auto i : v) {
if (i.at(0) == toupper('a')) {
cout << i << endl;
v.erase(remove(v.begin(), v.end(), i));
}
}
dis(v);
}
Can someone help me to find the error?
Since you already tried to implement the erase-remove-idiom, that's how it can be used in this case:
v.erase(std::remove_if(v.begin(), v.end(), [](const std::string &item) {
return std::toupper(item.at(0)) == 'A';
}), v.end());
Here I assumed, that i.at(0) == toupper('a') is a typo and should be toupper(i.at(0)) == 'A'.
Write your deletion loop like this:
for ( auto it = std::begin( v ); it != std::end( v ); )
{
if ( toupper( it->at( 0 ) ) == 'A' )
it = v.erase( it );
else
++it;
}
If you do it the way you're doing it you'll invalidate the iterator and then never reassign it a valid iterator which is needed to correctly loop through the vector.
The problem here :
for (auto i : v) {
if (i.at(0) == toupper('a')) {
cout << i << endl;
v.erase(remove(v.begin(), v.end(), i));
}
}
is that you are modifying the vector inside the loop with erase() which invalidates the iterators used internally to implement the for range loop.
The loop is a syntactic sugar for something like this :
{
auto&& range = v;
auto&& first = std::begin(v); // obtained once before entering the loop
auto&& last = std::end(v); // obtained once before entering the loop
for (; first != last; ++first)
{
auto i = *first; // first will be invalid the next time after you call erase()
if (i.at(0) == toupper('a')) {
cout << i << endl;
v.erase(remove(v.begin(), v.end(), i)); // you are invalidating the iterators and then dereferencing `first` iterator at the beginning of the next cycle of the loop
}
}
}
Why calling erase() invalidates the vector ?
This is because a vector is like a dynamic array which stores its capacity (whole array size) and size (current elements count), and iterators are like pointers which point to elements in this array
So when erase() is called it will rearrange the array and decrease its size, so updating the end iterator and your first iterator will not be pointing to the next item in the array as you intended . This illustrates the problem :
std::string* arr = new std::string[4];
std::string* first = arr;
std::string* last = arr + 3;
void erase(std::string* it)
{
std::destroy_at(it);
}
for (; first != last; ++first)
{
if (some_condition)
erase(first); // the last element in the array now is invalid
// thus the array length is now considered 3 not 4
// and the last iterator should now be arr + 2
// so you will be dereferencing a destoryed element since you didn't update your last iterator
}
What to learn from this ?
Never do something which invalidates the iterators inside for range loop.
Solution:
Update iterators at each cycle so you always have the correct bounds :
auto&& range = v;
auto&& first = std::begin(v); // obtained once before entering the loop
auto&& last = std::end(v); // obtained once before entering the loop
for (; first != last;)
{
auto i = *first;
if (i.at(0) == toupper('a'))
{
first = v.erase(remove(v.begin(), v.end(), i));
last = std::end(v);
}
else
{
++first;
}
}
I'm trying to write a program where given a vector, you use iterators to compare the first and last number of the vector, then moves in and compares the next ones. I wrote the for loop to do that, but am unsure how to make it stop once they reach the center of the vector.
For the for loop I have:
for (a = v.begin(), b = v.rbegin(); a != v.end(), b != v.rend(); a++, b++)
where a is the forward iterator and b is a backwards iterator.
My assumption is that I need to change the condition of the for loop, but I'm unsure to what.
So bear in mind that std::vector<T>::iterator is a random-access iterator, which means that it has operator< defined.
Using this, and using the std::reverse_iterator<Iterator>::base() member function, we can rewrite your for-loop to the following:
auto a = v.begin();
auto b = v.rbegin();
for (; a < b.base(); ++a, ++b)
{
// Do stuff...
}
First of all you need to use && and not the , operator in the comparison, which doesn't do what you think it does.
For your specific question you just keep going until both iterators reach each other, you can obtain the underlying std::iterator of a std::reverse_iterator through base(), eg:
template<typename T> bool isPalindrome(const std::vector<T>& data)
{
for (auto it = data.begin(), it2 = data.rbegin(); it != data.end() && it2 != data.rend() &&
it != it2.base(); ++it, ++it2)
if (*it != *it2)
return false;
return true;
}