【问题标题】:How to deal with last comma, when making comma separated string? [duplicate]制作逗号分隔字符串时如何处理最后一个逗号? [复制]
【发布时间】:2011-10-05 07:01:36
【问题描述】:

可能的重复:
Don't print space after last number
Printing lists with commas C++

#include <vector>
#include <iostream>
#include <sstream>
#include <boost/foreach.hpp>
using namespace std;

int main()
{
   vector<int> VecInts;

   VecInts.push_back(1);
   VecInts.push_back(2);
   VecInts.push_back(3);
   VecInts.push_back(4);
   VecInts.push_back(5);

   stringstream ss;
   BOOST_FOREACH(int i, VecInts)
   {
      ss << i << ",";
   }

   cout << ss.str();

   return 0;
}

打印出来:1,2,3,4,5, 但是我想要:1,2,3,4,5

我怎样才能以优雅的方式实现这一目标?

我发现我对“优雅”的含义有些混淆:例如在我的循环中没有放慢“if-clause”的速度。想象一下向量中有 100.000 个条目!如果这就是您所提供的全部内容,我宁愿在完成循环后删除最后一个逗号。

【问题讨论】:

  • 我也一直想知道这一点。
  • @Matthieu 我认为这不是骗子。当使用BOOST_FOREACH 时,没有明显的方法来检测开始或结束(你不能简单地检查index == 0it == c.begin() 或其他东西)。所以我不会把这个当成骗子来关闭。
  • @Johannes:我发现答案与“BOOST_FOREACH”无关。最简单的方法似乎是将替代变量放在一边,不管迭代方法是什么,然后……把问题转过来,检测 first 迭代而不是最后一个迭代。
  • 您可以尝试我们的 pretty printer 用于所有容器 :-)
  • @Matt 但这对于 for(int i = ..; i &lt; ...; i++) 循环来说是错误的方式,对于 for(iterator i = .. 循环来说也是错误的方式。两个循环都可以将i.. 进行比较,而不是引入替代变量。这个问题的难点在于没有迭代器和索引变量可以使用。投票决定重新开放。这些问题是相关的,但我不认为它们是“完全重复”

标签: c++ string boost stringstream csv


【解决方案1】:

这个怎么样:

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

int main()
{
   std::vector<int> v;

   v.push_back(1);
   v.push_back(2);
   v.push_back(3);
   v.push_back(4);
   v.push_back(5);

   std::ostringstream ss;
   if(!v.empty()) {
      std::copy(v.begin(), std::prev(v.end()), std::ostream_iterator<int>(ss, ", "));
      ss << v.back();
   }
   std::cout << ss.str() << "\n";
}

无需添加额外的变量,甚至不依赖于 boost!实际上,除了“循环中没有附加变量”的要求之外,可以说甚至没有循环:)

【讨论】:

  • 这是迄今为止我看到的最好的答案,我在任何建议的副本中都没有看到它!
  • 我添加了一个if(v.empty()) return; 行,把整个东西改成了一个模板函数,写了一些测试,我真的很满意。它将直接转到我的 StrUtils.hpp 标题。 ;-) 如果我没有看到任何意想不到的反对意见,这将很快被标记为正确答案。
【解决方案2】:

检测前一个总是很棘手,检测第一个非常容易。

bool first = true;
stringstream ss;
BOOST_FOREACH(int i, VecInts)
{
  if (!first) { ss << ","; }
  first = false;
  ss << i;
}

【讨论】:

  • 现在这是一种真正的替代方法!
【解决方案3】:

使用 Boost Spirit 的 Karma - 以速度快着称。

#include <iostream>
#include <vector>
#include <boost/spirit/include/karma.hpp>

int main()
{
  std::vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);

  using namespace boost::spirit::karma;
  std::cout << format(int_ % ',', v) << std::endl;
}

【讨论】:

  • 事实上它以比 iostream 更快(在某些标准库实现中相当慢)而闻名。
  • 这也是一个很好的解决方案。但我不得不选择,我觉得“纯 stl”版本。为我感到羞耻。 ;-)
  • 我不怪你 - 如果你只想打印一些整数,Karma 有点重量级!
  • 我对其进行了基准测试,将其与使用 10000 int 的“纯 stl”版本进行了比较。 windows,gcc,发布版本:PureStl:126,Boost:2658。 (不过我不确定我是否进行了可靠的测试。)
  • @AudioDroid:你确定编译时优化了吗? Karma 在很大程度上依赖于繁重的模板机制,因此它在很大程度上依赖于优化来获得不错的速度。
【解决方案4】:

试试:

if (ss.tellp ())
{
   ss << ",";
}
ss << i;

或者,如果“如果”让您担心:

char *comma = "";
BOOST_FOREACH(int i, VecInts)
{
   ss << comma << i;
   comma = ",";
}

【讨论】:

  • 如果你做出了正确的 C++,你会发现它远非优雅。
  • @Jan:好的,它现在是有效的 C++ 而不是简单的伪代码。
  • @PoweRoy:它没有删除任何东西——它是在之前添加而不是添加初始的。
  • 哇。我真的很喜欢第二个想法。杰出的。 :-D。嗯,比我每次都分配逗号。 :-/
  • @AudioDroid:当优化器处理时,分配应该只是一个“mov”指令。
【解决方案5】:

就个人而言,我喜欢一种不会导致潜在内存分配的解决方案(因为字符串增长得比需要的大)。由于分支目标缓冲,循环体内的额外 if 应该是易于处理的,但我会这样做:

#include <vector>
#include <iostream>

int main () {
    using std::cout;
    typedef std::vector<int>::iterator iterator;

    std::vector<int> ints;    
    ints.push_back(5);
    ints.push_back(1);
    ints.push_back(4);
    ints.push_back(2);
    ints.push_back(3);


    if (!ints.empty()) {
        iterator        it = ints.begin();
        const iterator end = ints.end();

        cout << *it;
        for (++it; it!=end; ++it) {
            cout << ", " << *it;
        }
        cout << std::endl;
    }
}

或者,BYORA(带上您自己的可重用算法):

// Follow the signature of std::getline. Allows us to stay completely
// type agnostic.
template <typename Stream, typename Iter, typename Infix>
inline Stream& infix (Stream &os, Iter from, Iter to, Infix infix_) {
    if (from == to) return os;
    os << *from;
    for (++from; from!=to; ++from) {
        os << infix_ << *from;
    }
    return os;
}

template <typename Stream, typename Iter>
inline Stream& comma_seperated (Stream &os, Iter from, Iter to) {
    return infix (os, from, to, ", ");
}

这样

...
comma_seperated(cout, ints.begin(), ints.end()) << std::endl;

infix(cout, ints.begin(), ints.end(), "-") << std::endl;
infix(cout, ints.begin(), ints.end(), "> <") << std::endl;
...

输出:

5, 1, 4, 2, 3
5-1-4-2-3
5> <1> <4> <2> <3

巧妙的是,它适用于每个输出流、任何具有前向迭代器的容器、任何中缀和任何中缀类型(有趣的是,例如当您使用宽字符串时)。

【讨论】:

  • 中缀方法缺少对from == to 的初始检查(我不是投反对票的人,也不认为这种微不足道的遗漏是投反对票的理由)。
  • 你是对的。将解决这个问题:)
  • @phresnel:实际上我认为它甚至可以与InputIterator 一起使用,例如std::cin。那些不吃补充论点的棘手部分:/
【解决方案6】:

我喜欢将测试移到循环之外。
它只需要执行一次。所以先做吧。

像这样:

if (!VecInts.empty())
{
    ss << VecInts[0]

    for(any loop = ++(VecInts.begin()); loop != VecInts.end(); ++loop)
    {
        ss << "," << *loop;
    }
}

【讨论】:

    【解决方案7】:

    您可以在末尾修剪字符串,或者使用单个 for 循环而不是 foreach 并且在最后一次迭代时不要连接

    【讨论】:

    • 返回并不总是可能的(想想打印到日志)。在结束之前停止意味着您可以检测到结束(在前向迭代器范围内不可能)。
    【解决方案8】:

    好吧,无论如何,如果你格式化为stringstream,你可以将结果字符串修剪一个字符:

    cout << ss.str().substr(0, ss.str().size() - 1);
    

    如果字符串为空,则第二个参数为-1,表示一切正常且不会崩溃,如果字符串非空,则始终以逗号结尾。

    但如果你直接写入输出流,我从来没有发现比 first 标志更好的东西。

    除非您想使用 boost.string algo 中的 join

    【讨论】:

    • boost::join(...) 听起来很有希望。您介意提供一个具有给定上下文的示例吗?
    • @AudioDroid: @AudioDroid: boost::join(boost::make_transform_iterator(v, boost::lexical_cast&lt;std::string, int&gt;), ",") 应该可以解决问题(使用来自 boost 的更多位),但我不太确定这是否足以使用词法转换。
    • (at)Jan Hudec:不幸的是,我的编译器不喜欢代码。也许我需要 boost 的编译版本。我只有在不编译库的情况下工作的功能。 :-/
    【解决方案9】:

    这样就可以了

    stringstream ss;
    BOOST_FOREACH(int const& i, VecInts)
    {
       if(&i != &VecInts[0])
         ss << ", ";
       ss << i;
    }
    

    我怀疑“优雅”是指“不引入新变量”。但我想如果我找不到其他东西,我会做“不优雅”的事。还是很清楚的

    stringstream ss;
    bool comma = false;
    BOOST_FOREACH(int i, VecInts)
    {
       if(comma)
         ss << ", ";
       ss << i;
       comma = true;
    }
    

    想象一下向量中有 100.000 个条目!如果这就是您所提供的全部内容,我宁愿在完成循环后删除最后一个逗号。

    您是说打印ss &lt;&lt; i 是一条机器指令。来吧,执行该表达式将执行大量if 和内部循环。与此相比,您的 if 将一事无成。

    【讨论】:

      【解决方案10】:

      cout &lt;&lt; ss.str()&lt;&lt;"\b" &lt;&lt;" ";

      您可以添加“\b”退格

      这将覆盖多余的“,”。

      例如:

      int main()
      {
          cout<<"Hi";
          cout<<'\b';  //Cursor moves 1 position backwards
          cout<<" ";   //Overwrites letter 'i' with space
      }
      

      所以输出是

      H
      

      【讨论】:

      • 这不会删除任何内容。它只会将输出光标向左移动一个。
      • @phresnel 我打算这样做,纠正我的解决方案谢谢:)
      • 里面还有“这将删除”
      • @phresnel 现在是这个吗?
      • 不幸的是,它也不会覆盖它。 \b 完全是一个“输出光标控制字符”,它对输入没有任何作用。
      猜你喜欢
      • 1970-01-01
      • 2018-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-20
      • 1970-01-01
      • 2014-09-14
      • 1970-01-01
      相关资源
      最近更新 更多