tags:

views:

112

answers:

7

Currently I have a map that prints out the following

map<string, map<int,int> > mapper;
map<int,int>::iterator inner;
map<string, map<int,int> >::iterator outer;


for(outer = mapper.begin(); outer != mapper.end(); outer++){
    cout<<outer->first<<": ";
  for(inner = outer->second.begin(); inner != outer->second.end(); inner++){
      cout<<inner->first<<","<<inner->second<<",";
  }
}

As of now this prints out the following

  stringone: 1,2,3,4,6,7,8,
  stringtwo: 3,5,6,7,
  stringthree: 2,3,4,5,

What i want it to print out is

  stringone: 1,2,3,4,6,7,8
  stringtwo: 3,5,6,7
  stringthree: 2,3,4,5

how can i check for the end of the map inside my inner map? Any help would be appreciated Thank you

+1  A: 

You could store the result in a string for example and trim the last character after the for.

Brian R. Bondy
+9  A: 

Change your output line to print the comma first and only if it is not the first element:

if (inner != outer->second.begin())
    std::cout << ",";
std::cout << inner->first << "," << inner->second;
James McNellis
This leaves an if test used only once inside a potentially long loop, it may be optomised and unrolled by the compiler but it is not gauranteed.
Greg Domjan
@Greg: True, but in my opinion it's cleaner than unrolling the first iteration through the loop, and it's unlikely to have a _significant_ performance impact relative to the actual operation of writing to the stream.
James McNellis
Amusing, I always unrolled the first iteration. I certainly wish we had some kind of greater operator for this.
Matthieu M.
is there a else after the std::cout<< ","; ?
eNetik
do you mean print the comma first, only if it is not the last element?
eNetik
@eNetik: No, there is no else. You always want to print the element. And no, you print the comma first unless it is the first element. In any case, the `infix_ostream_iterator` recommended by Jerry Coffin is pure awesomeness, and I'd recommend just using that.
James McNellis
@James Mcnellis: That does work but now instead of having extra commas in the end i get extra commas in the begining.. I would use what jerry Coffin explained but.. i dont understand it :/..This is what the output now isstringone: ,1,2,3,4,6,7,8
eNetik
@eNetik: Using this exact code, you will get exactly the behavior requested in the question (i.e., commas between the elements but not at the beginning or end). If you don't already have one, you should consider getting one of the introductory books from [The Definitive C++ Book Guide and List](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list).
James McNellis
wait nevermind i got it. I just had to add one more on top of your codeif(inner == outer->second.first(){ cout<<inner->first<<","<<inner->second;}Thanks for your help!
eNetik
+1  A: 

You need to modify your inner loop so that it prints the comma separator at the start for each iteration other than the first, something like:

// inside the loop
cout << ( inner == outer->second.begin() ? "" : "," ) << inner->first << "," << inner->second;
Edric
Why add an extra bool, already have the iterator and begin/end to tell this info.
Greg Domjan
Good point - made that change.
Edric
A: 

You have specified how you want to solve your problem technically 'finding the end of the loop' but the formatting you are trying to resolve can be managed in other ways.

Unroll the inner loop, and move the seperator.

for(m = mapper.begin(); m != mapper.end(); m++){
    cout<<m->first<<": ";

  if( inner = m->second.begin() != m->second.end() ) {
    cout<<inner->first<<","<<inner->second;
    ++inner
    for(; inner != m->second.end(); ++inner){
      cout<<","<<inner->first<<","<<inner->second;
    }
  }
}

This means you don't have to do a in if test in each iteration of the loop

Greg Domjan
+7  A: 

I'd change it to use std::copy, which I'd use along with an infix_ostream_iterator.

Edit: I'd also note for the record that what you're doing looks an awful lot like an std::multimap. If you don't want to use a multimap, it still looks like a lot simpler way to go would be something like:

std::ostream &operator<<(std::ostream &os, std::vector<int> const &v) { 
    std::copy(v.begin(), v.end(),
              infix_ostream_iterator<int>(os, ","));
    return os;
}

std::map<std::string, std::vector<int> > mapper;

std::copy(mapper.begin(), mapper.end(), 
          std::ostream_iterator<std::vector<int> >(std::cout, "\n");
Jerry Coffin
Very nice. That's getting added to my utilities library.
James McNellis
@James: yes, it is *extremely* handy to have around. In fact, if we'd come up with it in time, I probably would have tried to get it added to the standard. Maybe in TR2...
Jerry Coffin
This is interesting, seems a bit heavy, but it's nice you can easily pick a subrange.
Greg Domjan
I suppose it relies on the fact that the `if` statement should be optimized away by branch prediction for performance ?
Matthieu M.
@Matthieu: for the most part it just ignores that aspect -- the cost of an if statement is typically measured in nanoseconds, but of an I/O in at least 10s of milliseconds, so we're talking about a minuscule fraction of a percent, even at most. Yes, branch prediction will typically reduce that still further, but even with no branch prediction at all, you'd have to work *really* hard to dream up a situation in which you could measure the effect of the if statement.
Jerry Coffin
Ah. But you are assuming that we wish to perform an IO here. I use streams for other operations, like string concatenation or split, without performing any IO. Yet I appreciate this answer, optimizing the if out does seem like a micro optimization. It's just that I expect every possible optimization from a library :)
Matthieu M.
@Matthieu: Even if you use a stringstream as the target, you're still be hard put to measure the effect of a single if statement in the loop instead of out of the loop. I expect reasonable optimizations from libraries, but not ever *possible* optimization.
Jerry Coffin
+2  A: 
  for(inner = m->second.begin(); inner != m->second.end(); inner++){ 
      cout<<inner->first<<","<<inner->second;
      if (next(inner) != m->second.end()){
          cout<<",";
      }
  } 

Edit: It has been pointed out that map::iterator doesn't support addition, and that a suitable substitute is a nonstandard next function. I've updated the above code, and here's my own version of next.

template<typename T>
T next(T incrementable)
{
    return ++incrementable;
}
Mark Ransom
Why 'increment' inner twice for each loop, once for the loop and once to a temporary for comparison?
Greg Domjan
@Greg, you're correct it's redundant, but it retains the canonical form of the for loop making it easy to understand. Write for the programmer first, the computer second.
Mark Ransom
inner+1 will get compile error
eNetik
A `map` is only bidirectionally iterable, so `+` isn't supported. You could use `std::next(it)` if your implementation supports that, and if it doesn't it's a one-line function that is most useful and is trivial to implement.
James McNellis
@eNetik, thanks for pointing out the flaw. See my correction.
Mark Ransom
@James McNellis, thanks for the suggested alternate. I hope the one-liner I provided was what you had in mind. I notice my Microsoft C++ doesn't include one out of the box.
Mark Ransom
@Mark: It's a new feature in C++0x and was added in Visual C++ 2010. That's basically what the implementation is (I think it takes a second parameter of how many positions forward it should increment the iterator, but that's not needed in this case).
James McNellis
A: 

Change your inner loop to this:

if(outter->second.size()) {
    inner = outter->second.begin();
    while(true) {
        cout<<inner->first<<","<<inner->second;
        if(++inner != outer->second.end()) {
            cout<<",";
        } else {
            break;
        }
    }
}
Stephen Chu