2

Many times when creating a grammatical list (with comas), I use similar code to the following:

std::stringstream list;
int i = 0;
for (auto itemListIt = itemList.begin(); itemListIt != itemList.end(); itemListIt++)
{
    list << *itemListIt;
    if (i < itemList.size() - 1) list << ", ";
    i++;
}

Is there some more concise way do this, perhaps without the extra variable - 'i'?

d-_-b
  • 6,555
  • 5
  • 40
  • 58
  • possible duplicate of [Printing lists with commas C++](http://stackoverflow.com/questions/3496982/printing-lists-with-commas-c) – Steve Jessop Mar 15 '12 at 09:24

5 Answers5

3

Why not test what you're really interested in; "Is there another element after this one?".

std::stringstream list;
for (auto it = roomList.begin(); it != itemList.end(); it++)
{
    list << *it;
    if ( it+1 != itemList.end() ) list << ", ";
}
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • I though 'it' pointed to the element in the vector. Won't 'it+1' just add 1 to the address? – d-_-b Mar 15 '12 at 07:39
  • 1
    Yes, 'it' does point to the element in the vector. but 'it+1' won't add 1 to the address because 'it' is not a C++ pointer. It's an iterator and incrementing an iterator makes it point to the next element. (Not everything that points to something is a regular pointer. Iterators point to things, but they're not normal pointers.) – David Schwartz Mar 15 '12 at 07:41
  • Another alternative is to output the comma before each item, except the first which is easy to deduce. – Retired Ninja Mar 15 '12 at 07:50
  • @DavidSchwartz, I thought only increment and decrement operators existed for c++ iterators. I guess it depends on the iterator. – d-_-b Mar 15 '12 at 08:00
  • As written, this only works for random access iterators. In C++11, you can use `std::next( it )` instead of `it + 1`; if you don't have C++11, you've surely got the equivalent in your tool box (but since he's using `auto`, we can safely assume C++11). – James Kanze Mar 15 '12 at 09:13
  • @DavidSchwartz While you can't count on it, it's possible that `it` is a regular pointer. Most early implementations of `std::vector` defined the iterator as `typedef T* iterator;`, which is a legal, according to the standard. (Most modern implementations have recognized that this is a bad idea, and do use a class type.) – James Kanze Mar 15 '12 at 09:16
  • The approach with `std::next` will work for forward iterators, but not input iterators. Input iterators are single-pass only, which means you'd have to increment `it` in the loop body *instead* of in the loop continuation condition, not as well as. – Steve Jessop Mar 15 '12 at 09:21
  • 1
    @JamesKanze: It will still work. Since adding one to a `T*` makes it point to the next `T` and vectors are contiguous in memory. – David Schwartz Mar 15 '12 at 09:27
  • @SteveJessop `std::next` requires at least a forward iterator. – James Kanze Mar 15 '12 at 11:28
  • @James: exactly, just like `operator+` requires a random access iterator. I'm making basically the same comment you did, but you remarked that Michael's code requires RandomAccessIterator, and I'm remarking that your code requires ForwardIterator. It's probably safe to assume that something called `itemList.begin()` returns at least a forward iterator, but the questioner has a known habit of naming a stream `list`, so I'm playing safe ;-) – Steve Jessop Mar 15 '12 at 11:35
  • @SteveJessop Playing safe probably doesn't hurt. In practice, I'll generally use the second form in my own answer, which avoids the problem. (Both of my solutions do work with input iterators.) – James Kanze Mar 15 '12 at 12:06
  • In the pseudo-code I'm using auto because it's cool, not because my compiler supports it. ;) – d-_-b Mar 16 '12 at 01:26
1

There are two simple solutions for this. The first is to use a while loop:

auto itemListIt = itemList.begin();
while ( itemListIt != itemList.end() ) {
    list << *itemListIt;
    ++ itemListIt;
    if ( itemListIt != itemList.end() ) {
        list << ", ";
    }
}

The second solution is to change the logic slightly: instead of appending a ", " if there is more to follow, prefix one if you're not the first element:

for ( auto itemListIt = itemList.begin(); itemListIt != itemList.end(); ++ itemListIt ) {
    if ( itemListIt != itemList.begin() ) {
        list << ", ";
    }
    list << *itemListIt;
}
James Kanze
  • 150,581
  • 18
  • 184
  • 329
1

You can loop over everything up to the next to last using --items.end().

Then output the last one using items.back().

#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
#include <iostream>

int main()
{
    std::ostringstream oss;

    std::vector<int> items;
    items.push_back(1);
    items.push_back(1);
    items.push_back(2);
    items.push_back(3);
    items.push_back(5);
    items.push_back(8);

    if(items.size() > 1)
    {
        std::copy(items.begin(), --items.end(),
                  std::ostream_iterator<int>(oss, ", "));
        oss << "and ";
    }
    // else do nothing

    oss << items.back();

    std::cout << oss.str();
}

Output:

1, 1, 2, 3, 5, and 8

Peter Wood
  • 23,859
  • 5
  • 60
  • 99
0

And if you want to do that with a map or other iterators that cannot do random access, as others have suggested, check for the first element:

std::stringstream query;
query << "select id from dueShipments where package in (";
for (auto it = packageList.begin(); it != packageList.end(); it++)
{
    if (it != packageList.begin()) query << ", ";
    query << it->second;
}
query << ")";
d-_-b
  • 6,555
  • 5
  • 40
  • 58
0

The following will work with any InputIterator input:

std::stringstream list;
auto it(std::begin(input)); //Or however you get the input
auto end(std::end(input));
bool first(true);
for (; it != end; ++it)
{
    if (!first) list << ", ";
    else first = false;
    list << *it;
}

Or without an extra variable:

std::stringstream list;
auto it(std::begin(input)); //Or however you get the input
auto end(std::end(input));
if (it != end)
{
    list << *it;
    ++it;
}
for (; it != end; ++it)
{
    list << ", " << *it;
}
Mankarse
  • 39,818
  • 11
  • 97
  • 141
  • It still has an extra the variable - first – d-_-b Mar 20 '12 at 01:54
  • @doobie: Yeah, I guess this doesn't really answer the question. That said, it is not possible to do this _without_ an extra variable or duplicate code (for input iterators). I've added the "duplicate code" version. – Mankarse Mar 20 '12 at 03:11
  • @doobie: Ugh, I am stupid :$. James Kanze's answer is also a valid way to do this. – Mankarse Mar 20 '12 at 23:51