关于漂亮的打印:使用逗号C ++打印列表

Printing lists with commas C++

我知道如何用其他语言来实现这一点,而不是C++,这是我不得不在这里使用的。

我有一组要打印到列表中的字符串,它们需要在每个字符串之间使用逗号,但不能使用尾随逗号。例如,在Java中,我将使用String Bu建器并在构建完字符串后将逗号删除。如何在C++中实现?

1
2
3
4
5
6
7
auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{

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

我最初尝试插入这个块来完成(将逗号打印移到这里)

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

我讨厌小事情把我绊倒。

编辑:谢谢大家。这就是为什么我在这里贴这样的东西。这么多好的答案,以不同的方式解决。经过一个学期的Java和汇编(不同的类),不得不在4天内完成一个C++项目,让我陷入了一个循环。不仅我得到了我的答案,我还有机会思考解决类似问题的不同方法。令人惊叹的。


使用Infix_迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 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

使用方法如下:

1
2
3
4
#include"infix_iterator.h"

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


因为每个人都决定用while循环来完成这项工作,所以我将用for循环给出一个示例。

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


在即将到来的C++ C++ 17准备编译器中,可以使用EDCOX1×13:

1
2
3
4
5
6
7
8
9
10
11
12
#include
#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,","));
}

使用GCC 6.0 SVN和Clang 3.9 SVN的实况示例


一种常见的方法是在循环之前打印第一个项目,并且只在剩余项目上循环,在每个剩余项目之前预打印一个逗号。

或者,您应该能够创建自己的流,该流保持行的当前状态(在endl之前),并将逗号放在适当的位置。

编辑:您也可以使用T.E.D.建议的中间测试循环。它类似于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out <<",";
        }
    }
}

我首先提到了"循环前打印第一项"方法,因为它使循环体非常简单,但任何方法都可以正常工作。


假设输出流是大致正常的,因此向其写入空字符串实际上不起任何作用:

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


像这样?

1
2
3
4
5
6
while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout <<",";
}


有很多聪明的解决方案,而且太多的解决方案会在不让编译器完成其工作的情况下把代码弄乱,使代码失去拯救的希望。

显而易见的解决方案是,在特殊情况下,第一次迭代:

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

这是一个非常简单的模式:

  • 不会破坏循环:每一个元素都将被迭代一眼,这仍然是显而易见的。
  • 不仅可以放置分隔符,也可以实际打印列表,因为else块和循环体可以包含任意语句。
  • 它可能不是绝对最有效的代码,但单个预测良好的分支的潜在性能损失很可能被庞大的庞然大物std::ostream::operator<<所掩盖。


    我做分隔符(任何语言)的典型方法是使用中间测试循环。C++代码将是:

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

    (注:如果关键字可能为空,则需要在循环之前进行额外的if检查)

    大多数其他的解决方案最终都会对每个循环迭代进行一次完整的额外测试。你在做I/O,所以这所花的时间不是一个大问题,但它冒犯了我的感情。


    试试这个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
        }
    }


    我建议您在lambda的帮助下简单地切换第一个字符。

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

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


    您使用的++操作符有点问题。

    你可以试试:

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

    这样,在将迭代器与keywords.end()进行比较之前,将对++进行评估。


    为了避免在循环中放置一个if,我使用这个:

    1
    2
    3
    4
    5
    6
    7
    8
    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();
    }

    它取决于向量类型,int,但是您可以使用一些助手来删除它。


    在python中,我们只写了:

    1
    print",".join(keywords)

    为什么不:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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();
    }

    然后像这样使用它:

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

    与上面的Python示例不同,其中EDCOX1×8是一个字符串,EDCOX1×9必须是字符串的可迭代的,在此C++示例中,分隔符和EDCOX1×9可以是任何可流的,例如。

    1
    2
    cout << join('
    '
    , keywords) << endl;


    我想这个应该管用

    1
    2
    3
    4
    5
    6
    7
    while (iter != keywords.end( ))
    {

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


    可能是这样……

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


    我使用了一个小助手类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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;
    };

    使用它:

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

    如果值是std::strings,您可以使用range-v3以声明性样式编写它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #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;
    }

    对于必须转换为字符串的其他类型,只需添加一个调用to_string的转换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #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;
    }

    使用Booost:

    1
    2
    3
    4
    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));

    得到一个包含向量的字符串,用逗号分隔。

    编辑:要删除最后一个逗号,只需发出:

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

    应采取以下措施:

    1
    2
    3
    4
     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();


    您可以使用do循环,重写第一次迭代的循环条件,并使用短路&&运算符和有效流为true的事实。

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


    另一种可能的解决方案,避免了if

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

    取决于你在循环中做什么。


    这个函数重载流运算符。是的,全局变量是邪恶的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include <iostream>
    #include <string>
    #include <vector>
    #include
    #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;
    }

    可以使用函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <functional>

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

    例子:

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

    我将使用类似这样的简单解决方案,并且应该适用于所有迭代器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
    }