Printing lists with commas C++

时光毁灭记忆、已成空白 提交于 2019-11-26 03:13:56

问题


I know how to do this in other languages, but not C++, which I am forced to use here.

I have a Set of Strings that I\'m printing to out in a list, and they need a comma between each one, but not a trailing comma. In java for instance, I would use a stringbuilder and just delete the comma off the end after I\'ve built my string. How do I do it in C++?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{

    out << *iter << \", \";
}
out << endl;

I initially tried inserting this block to do it (moving the comma printing here)

if (iter++ != keywords.end())
    out << \", \";
iter--;

I hate when the small things trip me up.

EDIT: Thanks everyone. This is why I post stuff like this here. So many good answers, and tackled in different ways. After a semester of Java and assembly (different classes), having to do a C++ project in 4 days threw me for a loop. Not only did I get my answer, I got a chance to think about the different ways to approach a problem like this. Awesome.


回答1:


Use an infix_iterator:

// infix_iterator.h 
// 
// Lifted from Jerry Coffin's 's prefix_ostream_iterator 
#if !defined(INFIX_ITERATOR_H_) 
#define  INFIX_ITERATOR_H_ 
#include <ostream> 
#include <iterator> 
template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> > 
class infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag,void,void,void,void> 
{ 
    std::basic_ostream<charT,traits> *os; 
    charT const* delimiter; 
    bool first_elem; 
public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 
    infix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0), first_elem(true) 
    {} 
    infix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d), first_elem(true) 
    {} 
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
    { 
        // Here's the only real change from ostream_iterator: 
        // Normally, the '*os << item;' would come before the 'if'. 
        if (!first_elem && delimiter != 0) 
            *os << delimiter; 
        *os << item; 
        first_elem = false; 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator*() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++(int) { 
        return *this; 
    } 
};     
#endif 

Usage would be something like:

#include "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));



回答2:


In an experimental C++17 ready compiler coming soon to you, you can use std::experimental::ostream_joiner:

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

Live examples using GCC 6.0 SVN and Clang 3.9 SVN




回答3:


Because everyone has decided to do this with while loops, I'll give an example with for loops.

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}



回答4:


Assuming a vaguely normal output stream, so that writing an empty string to it does indeed do nothing:

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}



回答5:


One common approach is to print the first item prior to the loop, and loop only over the remaining items, PRE-printing a comma before each remaining item.

Alternately you should be able to create your own stream that maintains a current state of the line (before endl) and puts commas in the appropriate place.

EDIT: You can also use a middle-tested loop as suggested by T.E.D. It would be something like:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

I mentioned the "print first item before loop" method first because it keeps the loop body really simple, but any of the approaches work fine.




回答6:


Something like this?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}



回答7:


There are lots of clever solutions, and too many that mangle the code beyond hope of salvation without letting the compiler do its job.

The obvious solution, is to special-case the first iteration:

bool first = true;
for (auto const& e: sequence) {
   if (first) { first = false; } else { out << ", "; }
   out << e;
}

It's a dead simple pattern which:

  1. Does not mangle the loop: it's still obvious at a glance that each element will be iterated on.
  2. Allows more than just putting a separator, or actually printing a list, as the else block and the loop body can contain arbitrary statements.

It may not be the absolutely most efficient code, but the potential performance loss of a single well-predicted branch is very likely to be overshadowed by the massive behemoth that is std::ostream::operator<<.




回答8:


My typical method for doing separators (in any language) is to use a mid-tested loop. The C++ code would be:

for (;;) {
   std::cout << *iter;
   if (++iter == keywords.end()) break;
   std::cout << ",";
}

(note: An extra if check is needed prior to the loop if keywords may be empty)

Most of the other solutions shown end up doing an entire extra test every loop iteration. You are doing I/O, so the time taken by that isn't a huge problem, but it offends my sensibilities.




回答9:


Try this:

typedef  std::vector<std::string>   Container;
typedef Container::const_iterator   CIter;
Container   data;

// Now fill the container.


// Now print the container.
// The advantage of this technique is that ther is no extra test during the loop.
// There is only one additional test !test.empty() done at the beginning.
if (!data.empty())
{
    std::cout << data[0];
    for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
    {
        std::cout << "," << *loop;
    }
}



回答10:


In python we just write:

print ", ".join(keywords)

so why not:

template<class S, class V>
std::string
join(const S& sep, const V& v)
{
  std::ostringstream oss;
  if (!v.empty()) {
    typename V::const_iterator it = v.begin();
    oss << *it++;
    for (typename V::const_iterator e = v.end(); it != e; ++it)
      oss << sep << *it;
  }
  return oss.str();
}

and then just use it like:

cout << join(", ", keywords) << endl;

Unlike in the python example above where the " " is a string and the keywords has to be an iterable of strings, here in this C++ example the separator and keywords can be anything streamable, e.g.

cout << join('\n', keywords) << endl;



回答11:


There is a little problem with the ++ operator you are using.

You can try:

if (++iter != keywords.end())
    out << ", ";
iter--;

This way, ++ will be evaluated before compare the iterator with keywords.end().




回答12:


to avoid placing an if inside the loop, I use this:

vector<int> keywords = {1, 2, 3, 4, 5};

if (!keywords.empty())
{
    copy(keywords.begin(), std::prev(keywords.end()), 
         std::ostream_iterator<int> (std::cout,", "));
    std::cout << keywords.back();
}

It depends on the vector type, int, but you can remove it with some helper.




回答13:


Following should do:-

 const std::vector<__int64>& a_setRequestId
 std::stringstream strStream;
 std::copy(a_setRequestId.begin(), a_setRequestId.end() -1, std::ostream_iterator<__int64>(strStream, ", "));
 strStream << a_setRequestId.back();



回答14:


If the values are std::strings you can write this nicely in a declarative style with range-v3

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<std::string> const vv = { "a","b","c" };

    auto joined = vv | view::join(',');

    std::cout << to_<std::string>(joined) << std::endl;
}

For other types which have to be converted to string you can just add a transformation calling to_string.

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<int> const vv = { 1,2,3 };

    auto joined = vv | view::transform([](int x) {return std::to_string(x);})
                     | view::join(',');
    std::cout << to_<std::string>(joined) << std::endl;
}



回答15:


I think this should work

while (iter != keywords.end( ))
{

    out << *iter;
    iter++ ;
    if (iter != keywords.end( )) out << ", ";
}



回答16:


I use a little helper class for that:

class text_separator {
public:
    text_separator(const char* sep) : sep(sep), needsep(false) {}

    // returns an empty string the first time it is called
    // returns the provided separator string every other time
    const char* operator()() {
        if (needsep)
            return sep;
        needsep = true;
        return "";
    }

    void reset() { needsep = false; }

private:
    const char* sep;
    bool needsep;
};

To use it:

text_separator sep(", ");
for (int i = 0; i < 10; ++i)
    cout << sep() << i;



回答17:


Using boost:

std::string add_str("");
const std::string sep(",");

for_each(v.begin(), v.end(), add_str += boost::lambda::ret<std::string>(boost::lambda::_1 + sep));

and you obtain a string containing the vector, comma delimited.

EDIT: to remove the last comma, just issue:

add_str = add_str.substr(0, add_str.size()-1);



回答18:


Could be like so..

bool bFirst = true;
for (auto curr = keywords.begin();  curr != keywords.end(); ++curr) {
   std::cout << (bFirst ? "" : ", ") << *curr;
   bFirst = false;
}



回答19:


I suggest you simply switch the first character with the help of a lambda.

std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };                  

for (auto &k : keywords)
    std::cout << f() << k;



回答20:


I think this variant of @MarkB's answer strikes optimal balance of readability, simplicity and terseness:

auto iter= keywords.begin();
if (iter!=keywords.end()) {
    out << *iter;
    while(++iter != keywords.end())
        out << "," << *iter;
}
out << endl;



回答21:


I would go with something like this, an easy solution and should work for all iterators.

int maxele = maxele = v.size() - 1;
for ( cur = v.begin() , i = 0; i < maxele ; ++i)
{
    std::cout << *cur++ << " , ";
}
if ( maxele >= 0 )
{
  std::cout << *cur << std::endl;
}



回答22:


You can use a do loop, rewrite the loop condition for the first iteration, and use the short-circuit && operator and the fact that a valid stream is true.

auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
    out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;



回答23:


This one overloads the stream operator. Yes global variables are evil.

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

int index = 0;
template<typename T, template <typename, typename> class Cont>
std::ostream& operator<<(std::ostream& os, const Cont<T, std::allocator<T>>& vec)
{
    if (index < vec.size()) {
        if (index + 1 < vec.size())
            return os << vec[index++] << "-" << vec;
        else
            return os << vec[index++] << vec;
    } else return os;
}

int main()
{
    std::vector<int> nums(10);
    int n{0};
    std::generate(nums.begin(), nums.end(), [&]{ return n++; });
    std::cout << nums << std::endl;
}



回答24:


Another possible solution, which avoids an if

Char comma = '[';
for (const auto& element : elements) {
    std::cout.put(comma) << element;
    comma = ',';
}
std::cout.put(']');

Depends what you're doing in your loop.




回答25:


Can use functors:

#include <functional>

string getSeparatedValues(function<bool()> condition, function<string()> output, string separator)
{
    string out;
    out += output();
    while (condition())
        out += separator + output();
    return out;
}

Example:

if (!keywords.empty())
{
    auto iter = keywords.begin();
    cout << getSeparatedValues([&]() { return ++iter != keywords.end(); }, [&]() { return *iter; }, ", ") << endl;
}



回答26:


A combination of c++11 lambda and macro:

#define INFIX_PRINTER(os, sep)([&]()->decltype(os)&{static int f=1;os<<(f?(f=0,""):sep);return os;})()

Usage:

for(const auto& k: keywords)
    INFIX_PRINTER(out, ", ") << k;


来源:https://stackoverflow.com/questions/3496982/printing-lists-with-commas-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!