views:

4474

answers:

11

I'm trying to figure out the best way to determine if I'm in the last iteration of a loop over a map in order to do something like the following:

for (iter = someMap.begin(); iter != someMap.end(); ++iter) {
    bool last_iteration;
    // do something for all iterations
    if (!last_iteration) {
        // do something for all but the last iteration
    }
}

There seem to be several ways of doing this: random access iterators, the distance function, etc. What's the canonical method?

Edit: no random access iterators for maps!

+7  A: 

This seems like the simplest:

bool last_iteration = iter == (--someMap.end());
Torlack
Do iterators actually define arithmetic plus with integers? I didn't think so; but even if some of them do, I'm certain you couldn't rely on it for *every* container.
Daniel Spiewak
This doesn't work... there's no match for the [iterator type] + int operator!
cdleary
Bi-directional iterators (what map has) does not have + defined to manipulate it.
KTC
Code fixed... too late :)
Torlack
Is decrementing an iterator to an empty collection defined?
Tim Stewart
@Tim: If it's an empty collection we'll never enter the loop body. :-)
cdleary
A: 

You can just pull an element out of the map prior to iteration, then perform your "last iteration" work out of the loop and then put the element back into the map. This is horribly bad for asynchronous code, but considering how bad the rest of C++ is for concurrency, I don't think it'll be an issue. :-)

Daniel Spiewak
Maps ARE ordered - maybe you're thinking of a hash?
Mark Ransom
I was under the impression that Maps were implemented by default using hashtables. I will correct the answer. Thanks!
Daniel Spiewak
+9  A: 

Canonical? I can't claim that, but I'd suggest

final_iter = someMap.end();
--final_iter;
if (iter != final_iter) ...

Edited to correct as suggested by KTC. (Thanks! Sometimes you go too quick and mess up on the simplest things...)

Mark Ransom
Reminds everyone how post-decrement work again? ;-)
KTC
Decrement must be a predecrement or the finalIter will be end.
Torlack
CAVEAT: It requires bi-directional iterators, so it doesn't work in all STL collection classes.
Euro Micelli
Agree with Torlack, the post decrement will happen after the assignment...
Doug T.
You could just compare with the value at rbegin()
Jeff Yates
@ffpf, you can't because comparisons between forward iterators and reverse iterators don't work.
cdleary
One can compare against --(someMap.rbegin().base()), but that's just making it over complicated for no good reason. (It was what had originally thought to post LOL :D)
KTC
Of course, doh! I admit it's been a wee while since my last major C++ work. Amazing how quickly things leave the brain when they're not used.
Jeff Yates
Unless the map changes inside the loop, final_iter should be evaluated outside it. In this case, the map must be checked for emptiness, lest we run into the feared undefined behaviour.
Gorpik
+3  A: 

Modified Mark Ransom's so it actually work as intended.

finalIter = someMap.end();
--finalIter;
if (iter != final_iter)
KTC
Why was KTC's marked down? It is more correct than Mark's.
Torlack
It wasn't marked down. The questioner merely changed his/her mind on the accepted answer.
KTC
Thanks for the correction, I gave you an upvote for it.
Mark Ransom
A: 

A simple, yet effective, approach:

  size_t items_remaining = someMap.size();

  for (iter = someMap.begin(); iter != someMap.end(); iter++) {
    bool last_iteration = items_remaining-- == 1;
  }
Jason Etheridge
A: 

Full program:

#include <iostream>
#include <list>

void process(int ii)
{
   std::cout << " " << ii;
}

int main(void)
{
   std::list<int> ll;

   ll.push_back(1);
   ll.push_back(2);
   ll.push_back(3);
   ll.push_back(4);
   ll.push_back(5);
   ll.push_back(6);

   std::list<int>::iterator iter = ll.begin();
   if (iter != ll.end())
   {
      std::list<int>::iterator lastIter = iter;
      ++ iter;
      while (iter != ll.end())
      {
         process(*lastIter);
         lastIter = iter;
         ++ iter;
      }
      // todo: think if you need to process *lastIter
      std::cout << " | last:";
      process(*lastIter);
   }

   std::cout << std::endl;

   return 0;
}

This program yields:

 1 2 3 4 5 | last: 6
florin
+2  A: 
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <algorithm>

using namespace boost::lambda;

// call the function foo on each element but the last...
if( !someMap.empty() )
{
  std::for_each( someMap.begin(), --someMap.end(), bind( &Foo, _1 ) );
}

Using std::for_each will ensure that the loop is tight and accurate... Note the introduction of the function foo() which takes a single argument (the type should match what is contained in someMap). This approach has the added addition of being 1 line. Of course, if Foo is really small, you can use a lambda function and get rid of the call to &Foo.

ceretullis
+9  A: 

If you just want to use a ForwardIterator, this should work:

for ( i = c.begin(); i != c.end(); ) {
        iterator cur = i++;
        // do something, using cur
        if ( i != c.end() ) {
                // do something using cur for all but the last iteration
        }
}
camh
A: 

Here's my optimized take:

iter = someMap.begin();

do {
    // Note that curr = iter++ may involve up to three copy operations
    curr = iter;

    // Do stuff with curr

    if (++iter == someMap.end()) {
        // Oh, this was the last iteration
        break;
    }

    // Do more stuff with curr

} while (true);
Ates Goral
A: 

Thanks for that solution, I used that and Mark's solution worked

This is not an answer -- it should be deleted.
cdleary
+3  A: 

Surprised no one mentioned it yet, but of course boost has something ;)

Boost.Next (and the equivalent Boost.Prior)

Your example would look like:

for (iter = someMap.begin(); iter != someMap.end(); ++iter) {
    // do something for all iterations
    if (boost::next(iter) != someMap.end()) {
        // do something for all but the last iteration
    }
}
Pieter