Suppose I have a map<int, set<int>> called my_map and I want to start at the element with key value 5 and loop backward until I get to the beginning, and check each set for a certain criterion. (I need to go backward, because I want to break if the criterion is met.)
std::find returns a forward iterator, so the way I thought of to do it is
for (auto rit = std::make_reverse_iterator(++my_map.find(A[i])); rit != my_map.rend(); ++rit) {
//check conditions on rit->second
}
Is that the best way?
Related
We routinely iterate over containers with range-base for loop, or the pre-C++11 iterator based approach:
for(auto current = container.begin(); current != container.end(); ++current)
It is usually taught as a much preferred way over the "C-ish" iterations based on index increments:
for(auto currentId = 0; currentId != container.size(); ++currentId)
Yet, what if the requirement is to iterate every N th element, for example every third?
An erroneous rewrite of the above approach would give:
// Error
for(auto current = container.begin(); current != container.end(); current += 3)
Now, our programs exhibit undefined behaviour each time (container.size() % 3) != 0.
What would be the canonical C++ way to address such requirement?
Can it be done reasonably with iterator based iteration?
Should we fall-back to indexes (with comparison becoming <instead of !=)?
I want to use the for loops with iterators while using maps and want to run it for a specified range not from begin() end end(). I would want to use it for a range like from 3rd element to 5th element
I would want to use it for a range like from 3rd element to 5th
element
Since std::map's iterator is not a RandomAccessIterator, but only a BidirectionalIterator (you cannot write .begin() + 3), you can use std::next for this purpose:
for (auto it = std::next(m.begin(), 2); it != std::next(m.begin(), 5); ++it)
{
// ...
}
And remember - check your ranges to ensure, that you iterate over a valid scope.
This code should be pretty optimal and safe for corner cases:
int count = 0;
for( auto it = m.begin(); it != m.end(); ++it ) {
if( ++count <= 3 ) continue;
if( count > 5 ) break;
// use iterator
}
but the fact you are iterating an std::map this way shows most probably you are using a wrong container (or your logic for 3rd to 5th element is wrong)
I am just looking at a code, and I must say that I don't fully understand it yet.
vector<long>::iterator iter1;
vector<long>::iterator iter2;
while(m_vPitchMarks[0]<=vPitchPeriode[0])
{
iter1 = m_vPitchMarks.begin();
iter2 = vPitchPeriode.begin();
m_vPitchMarks.erase(iter1);
vPitchPeriode.erase(iter2);
if((m_vPitchMarks.size()==0)||(vPitchPeriode.size()==0))
break;
}
I am trying to break it down:
Here we will do a while-statement while the value of the first element of m_vPitchMarks is smaller than the value of the first element of vPitchPeriod.
while(m_vPitchMarks[0]<=vPitchPeriode[0])
{
}
Here we set something like a reference to the first element (element at index [0]) of both vectors.
iter1 = m_vPitchMarks.begin();
iter2 = vPitchPeriode.begin();
Now we erase all elements from m_vPitchMarks<> that have just this value.
For example, if iter1 had a value of 15, all elements in m_vPitchMarks<> that also have the value of 15 will be deleted, and the vector becomes shortened.
m_vPitchMarks.erase(iter1);
vPitchPeriode.erase(iter2);
Is that correct?
Thank you.
Almost as you say this:
while(m_vPitchMarks[0]<=vPitchPeriode[0])
will loop while the first value of m_vPitchMarks is less than or equal to the first value of vPitchPeriode.
Your explanation for this this code:
iter1 = m_vPitchMarks.begin();
iter2 = vPitchPeriode.begin();
m_vPitchMarks.erase(iter1);
vPitchPeriode.erase(iter2);
is not quite right. It simply removes the first value from both the vectors.
If you were using a std::deque or a std::list instead then you would just call pop_front to get the same result.
Then if we're out of values stop looping:
if((m_vPitchMarks.size()==0)||(vPitchPeriode.size()==0))
break;
Seems a particularly inefficient route given erasing an element from the front of a vector requires shifting every element that is left.
I have created a multimap as I have repeating keys. But I want do an efficient manipulation so that I can generate a new multimap with subsequent higher keys aligned. This is what I mean:
This is what I have:
key values
11 qwer
11 mfiri
21 iernr
21 ghfnfjf
43 dnvfrf
This is what I want to achive
key values
11 qwer,iernr
11 mfiri,iernr
21 iernr,dnvfrf
21 ghfnfjf,dnvfrf
43 dnvfrf
I have about 10 million entries so I am looking for something efficient.
In above value "qwer,iernr" is one string.
Here's a simple way to do it:
auto cur = map.begin();
auto next = map.upper_bound(cur->first);
for(; next != map.end(); next = map.upper_bound(cur->first))
{
for(; cur != next; ++cur)
{
cur->second += ", ";
cur->second += next->second;
}
}
... given a std::multimap<int, std::string> map;
However, any operation transforming 10m+ elements isn't going to be super fast.
Looks like straight-forward way would work fine. Map elements will be laid out in ascending order (assuming compare operator suits you). So just going through the equal ranges and modifying them with value of the element just after the range will do what you want.
Clone map (if you need the original), take first element, get equal_range() for its key, modify values with value of second iterator in the range (unless it is the last one). Get equal_range() for the key of second iterator. Repeat.
agree with Eugene ! also see following reference in terms of equal_range()
stl::multimap - how do i get groups of data?
To do this, you need to simply iterate through the map, while building the new map in order.
You can do this in two levels:
for (auto it=map.cbegin(); it != map.cend(); )
{
// The inner loop is over all entries having the same key
auto next_key_it=find_next_key_after(it);
for (; it != next_key_it; ++it) {
new_map.emplace_hint(new_map.end(), it->first, new_value(it->second, next_key_it));
}
}
The new_value function (or lambda) does the value transformation (or not, if the second parameter is map.end()).
The find_next_key_after(it) function returns the same as map.upper_bound(it->first), but could also be implemented as linear search for the first entry with different key.
It depends on your (expected) key distribution, which to use - if keys repeat a small, limited number of times, linear search is better; if the number of different keys is limited, with large equal key ranges, then upper_bound may be better.
For guaranteed complexity, linear search is better: The whole algorithm then has O(n) complexity. Which is as efficient as you can get.
std::vector<int> v = {1,2,3,4,5};
auto i = std::remove(v.begin(),v.end(),3);
for(auto j = v.begin(); j!= v.end();++j)
std::cout << *j;
Actual output : 12455
Where does extra 5 come from?
Desired output : 1245
How to achieve this?
remove doesnt actually remove the elements
Remove removes from the range [first, last) all elements that are equal to value. That is, remove returns an iterator new_last such that the range [first, new_last) contains no elements equal to value. 1 The iterators in the range [new_last, last) are all still dereferenceable, but the elements that they point to are unspecified. Remove is stable, meaning that the relative order of elements that are not equal to value is unchanged.`
std::remove algorithm works only using a pair of forward iterators and in general knows nothing about the underlying container.
You need to use the erase-remove idiom to actually remove the element i.e combine erase with remove
auto i = std::remove(v.begin(),v.end(),3);
v.erase(i,v.end());
for(auto j = v.begin(); j!= v.end();++j)
std::cout << *j;
Read the documentation for std::remove again.
The function does not remove elements from a container (in fact, it doesn't even know that a container is involved, as it only sees iterators), it merely moves values in a sequence and return a new iterator i such that all the interval [ begin .. i [ contains all non-removed elements in the original order. Elements left over in [ i .. end [ are unspecified, and it is your responsibility to eliminate that interval from a container (if you need it):
auto i = std::remove(...);
v.erase(i,v.end());
The reason why you have an additional 5 is that the typical removal algorithm copies values into holes left by removed values, and since values past the i iterator are never overwritten, they remain the same as in the original sequence. This behavior, however, is not reliable - just eliminate the values past i without reading them.
remove returns the new end. So the fix of your code is this:
std::vector<int> v = {1,2,3,4,5};
auto newEnd = std::remove(v.begin(),v.end(),3);//return value stored in newEnd
for(auto j = v.begin(); j!= newEnd ;++j) //note j!=newEnd
std::cout << *j;
Output:
1245
Check it out yourself : http://www.ideone.com/3AMD9
Actually, std::remove moves at beginning of the range all elements which don't satisfy predicate, AND returns iterator of the new end.
So usage is erase-remove idiom:
v.erase(std::remove(v.begin(), v.end(), 3), v.end());
In c++20, it can be simplified thanks to std::erase:
std::erase(v, 3);
It's seams that you are printing the n+1 position of the vector in the for() statement.
It should be:
for(auto j = v.begin(); j!= v.end();j++)
std::cout << *j;
j++ no ++j