【问题标题】:std::vector to string with custom delimiterstd::vector 到带有自定义分隔符的字符串
【发布时间】:2012-03-05 21:08:48
【问题描述】:

我想用自定义分隔符将vector 的内容复制到一个很长的string。到目前为止,我已经尝试过:

// .h
string getLabeledPointsString(const string delimiter=",");
// .cpp
string Gesture::getLabeledPointsString(const string delimiter) {
    vector<int> x = getLabeledPoints();
    stringstream  s;
    copy(x.begin(),x.end(), ostream_iterator<int>(s,delimiter));
    return s.str();
}

但我明白了

no matching function for call to ‘std::ostream_iterator<int, char, std::char_traits<char> >::ostream_iterator(std::stringstream&, const std::string&)’

我试过charT*,但我明白了

error iso c++ forbids declaration of charT with no type

然后我尝试使用charostream_iterator&lt;int&gt;(s,&amp;delimiter) 但我在字符串中得到了奇怪的字符。

谁能帮我理解编译器在这里期望什么?

【问题讨论】:

  • 是的,如果编译器好心地告诉你它所期望的类型,那不是很好。顺便说一句,你的最后一个元素后面也会有一个逗号。
  • 最优雅的方法是使用 boost::algorithm::join() ,如stackoverflow.com/a/6334153/2056686中所述

标签: c++ string vector


【解决方案1】:

Use delimiter.c_str() as the delimiter:

copy(x.begin(),x.end(), ostream_iterator<int>(s,delimiter.c_str()));

这样,您会得到一个指向字符串的const char*,这就是ostream_operator 对您的std::string 的期望。

【讨论】:

  • +1,但还要注意这将在输出中写入尾随 delimiter
  • 我不确定这里的性能。 stringstream 管理自己的缓冲区,因此它必须动态增长。在这里你在生成字符串之前就知道它的长度,所以你应该在连接之前保留缓冲区。
  • “你在生成字符串之前就知道它的长度”——或者无论如何你知道一个上限。如果 stringstream 的缓冲区呈指数增长,那么我不会担心性能,但我不知道是不是这样。
【解决方案2】:

C++11:

vector<string> x = {"1", "2", "3"};
string s = std::accumulate(std::begin(x), std::end(x), string(),
                                [](string &ss, string &s)
                                {
                                    return ss.empty() ? s : ss + "," + s;
                                });

【讨论】:

  • 看起来很整洁,但这不会在过程中创建很多字符串吗?有什么方法可以使用字符串流来改善这一点?
  • 不,stringsream 更慢。实际上,如果您只有字符串容器,最好编写像 code 字符串结果这样的普通 for 循环;结果.储备(128); for (auto &it: x) { if (!result.emty()) { result.append(","); } 结果。附加(它); } code
  • 听起来很合理,但这不是上面的代码所做的。它没有附加 - 它正在生成新的字符串。
  • 如果从begin(x)+1开始,以*begin(x)作为初始元素,可以避免条件。
  • 您传递非常量引用的任何原因?
【解决方案3】:

另一种方法:

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;

template <typename T>
string join(const T& v, const string& delim) {
    ostringstream s;
    for (const auto& i : v) {
        if (&i != &v[0]) {
            s << delim;
        }
        s << i;
    }
    return s.str();
}

int main() {
    cout << join(vector<int>({1, 2, 3, 4, 5}), ",") << endl;
}

(尽管 c++11 基于范围的 for 循环和 'auto')

【讨论】:

    【解决方案4】:

    这是对上面已经提供的两个答案的扩展,因为运行时性能似乎是 cmets 的一个主题。我会把它添加为 cmets,但我还没有这个权限。

    我使用 Visual Studio 2015 测试了 2 个实现的运行时性能:

    使用字符串流:

    std::stringstream result;
    auto it = vec.begin();
    result << (unsigned short)*it++;
    for (; it != vec.end(); it++) {
        result << delimiter;
        result << (unsigned short)*it;
    }
    return result.str();
    

    使用累积:

    std::string result = std::accumulate(std::next(vec.begin()), vec.end(),
        std::to_string(vec[0]),
        [&delimiter](std::string& a, uint8_t b) {
        return a + delimiter+ std::to_string(b);
    });
    return result;
    

    发布构建运行时性能接近,但有一些细微之处。

    累加实现稍快(20-50 毫秒,约 10-30% 的总运行时间(约 180 毫秒),在 256 个元素向量上进行 1000 次迭代)。然而,accumulate 的实现只有在 lambda 函数的 a 参数通过引用传递时才会更快。按值传递a 参数会导致类似的运行时差异有利于stringstream 实现。当直接返回结果字符串而不是分配给立即返回的局部变量时,accumulate 实现也有所改进。 YMMV 与其他 C++ 编译器。

    使用accumulate 时,Debug 构建速度会慢 5-10 倍,所以我认为上面几个 cmets 中提到的额外字符串创建已由优化器解决。

    我正在查看使用 vectoruint8_t 值的特定实现。完整的测试代码如下:

    #include <vector>
    #include <iostream>
    #include <sstream>
    #include <numeric>
    #include <chrono>
    
    using namespace std;
    typedef vector<uint8_t> uint8_vec_t;
    
    string concat_stream(const uint8_vec_t& vec, string& delim = string(" "));
    string concat_accumulate(const uint8_vec_t& vec, string& delim = string(" "));
    
    string concat_stream(const uint8_vec_t& vec, string& delimiter)
    {
        stringstream result;
    
        auto it = vec.begin();
        result << (unsigned short)*it++;
        for (; it != vec.end(); it++) {
            result << delimiter;
            result << (unsigned short)*it;
        }
        return result.str();
    }
    
    string concat_accumulate(const uint8_vec_t& vec, string& delimiter)
    {
        return accumulate(next(vec.begin()), vec.end(),
            to_string(vec[0]),
            [&delimiter](string& a, uint8_t b) {
            return a + delimiter + to_string(b);
        });
    }
    
    int main()
    {
        const int elements(256);
        const int iterations(1000);
    
        uint8_vec_t test(elements);
        iota(test.begin(), test.end(), 0);
    
        int i;
        auto stream_start = chrono::steady_clock::now();
        string join_with_stream;
        for (i = 0; i < iterations; ++i) {
            join_with_stream = concat_stream(test);
        }
        auto stream_end = chrono::steady_clock::now();
    
        auto acc_start = chrono::steady_clock::now();
        string join_with_acc;
        for (i = 0; i < iterations; ++i) {
            join_with_acc = concat_accumulate(test);
        }
        auto acc_end = chrono::steady_clock::now();
    
        cout << "Stream Results:" << endl;
        cout << "    elements: " << elements << endl;
        cout << "    iterations: " << iterations << endl;
        cout << "    runtime: " << chrono::duration<double, milli>(stream_end - stream_start).count() << " ms" << endl;
        cout << "    result: " << join_with_stream << endl;
    
        cout << "Accumulate Results:" << endl;
        cout << "    elements: " << elements << endl;
        cout << "    iterations: " << iterations << endl;
        cout << "    runtime: " << chrono::duration<double, milli>(acc_end - acc_start).count() << " ms" << endl;
        cout << "    result:" << join_with_acc << endl;
    
        return 0;
    }
    

    【讨论】:

    • 我喜欢您提供的性能数据——这对于以一种或另一种方式做出决定非常有用,谢谢!
    • 简单地说:concat_stream()concat_accumulate() 仅适用于非空向量。
    【解决方案5】:
    std::string Gesture::getLabeledPointsString(const std::string delimiter) {
      return boost::join(getLabeledPoints(), delimiter);
    }
    

    此时我对引入getLabeledPointsString 不太确信;)

    【讨论】:

    • ehehehehheheh 有没有没有boost 的cpp? +1 提升,谢谢!
    • @nkint:哦,你当然可以在没有提升的情况下进行编程。但这与没有库的 Python 一样困难:您只需要自己创建所有工具;)
    【解决方案6】:
    string join(const vector<string> & v, const string & delimiter = ",") {
        string out;
        if (auto i = v.begin(), e = v.end(); i != e) {
            out += *i++;
            for (; i != e; ++i) out.append(delimiter).append(*i);
        }
        return out;
    }
    

    几点:

    • 您不需要额外的条件来避免额外的尾随分隔符
    • 确保向量为空时不会崩溃
    • 不要做一堆临时的(例如不要这样做:x = x + d + y)

    【讨论】:

      【解决方案7】:

      我知道这是一个老问题,但我有一个类似的问题,以上答案都不适合我的所有需求,所以我将在这里发布我的解决方案。

      我的要求是:

      • 我需要一个能够使用任何可迭代容器和任何数据类型的通用解决方案,当然对于自定义数据类型,您必须提供合适的operator&lt;&lt;()
      • 我需要一种简单的方法来对数据应用转换(例如,默认情况下,int8_tuint8_tchars 处理为 std::stringstream:也许这是你想要的,也许不是,所以我希望能够做出这样的选择)
      • 我希望能够将分隔符指定为字符串文字,但也接受chars 和std::strings
      • 我喜欢有添加封闭字符的能力,但这可能是非常个人的品味

      这假设 C++11。 我选择使用std::stringstream,因为它实现了一种标准但仍可自定义的方式来将某些内容转换为字符串。 任何 cmets 都非常受欢迎。

      #include <iterator>
      #include <sstream>
      #include <string>
      #include <iostream> // used only in main
      #include <vector> // used only in main
      
      template< typename T >
      typename std::iterator_traits< T >::value_type
      identity(typename std::iterator_traits< T >::value_type v) {
        return v;
      }
      
      template< typename T > using IdentityType = decltype(identity< T >);
      
      template< class InItr,
                typename StrType1 = const char *,
                typename StrType2 = const char *,
                typename StrType3 = const char *,
                typename Transform = IdentityType< InItr > >
      std::string join(InItr first,
                       InItr last,
                       StrType1 &&sep = ",",
                       StrType2 &&open = "[",
                       StrType3 &&close = "]",
                       Transform tr = identity< InItr >) {
      
        std::stringstream ss;
      
        ss << std::forward< StrType2 >(open);
      
        if (first != last) {
      
          ss << tr(*first);
      
          ++first;
        }
      
        for (; first != last; ++first)
          ss << std::forward< StrType1 >(sep) << tr(*first);
      
        ss << std::forward< StrType3 >(close);
      
        return ss.str();
      }
      
      
      int main(int argc, char** argv) {
      
        const std::vector< int > vec{2, 4, 6, 8, 10};
      
        std::cout << join(vec.begin(), vec.end()) << std::endl;
        std::cout << join(vec.begin(), vec.end(), "|", "(", ")",
                          [](int v){ return v + v; }) << std::endl;
      
        const std::vector< char > vec2{2, 4, 6, 8, 10};
        std::cout << join(vec2.begin(), vec2.end()) << std::endl;
        std::cout << join(vec2.begin(), vec2.end(), "|", "(", ")",
                [](char v){ return static_cast<int>(v); }) << std::endl;
      }
      

      输出类似:

      [2,4,6,8,10]
      (4|8|12|16|20)
      [<unprintable-char>,<unprintable-char>,<unprintable-char>,
      ]
      (2|4|6|8|10)
      

      【讨论】:

        【解决方案8】:
        int array[ 6 ] = { 1, 2, 3, 4, 5, 6 };
        std::vector< int > a( array, array + 6 );
        stringstream dataString; 
        ostream_iterator<int> output_iterator(dataString, ";"); // here ";" is delimiter 
        std::copy(a.begin(), a.end(), output_iterator);
        cout<<dataString.str()<<endl;
        

        输出= 1;2;3;4;5;6;

        【讨论】:

        • 一般来说,如果答案包含对代码的用途的解释,以及为什么在不介绍其他人的情况下解决问题的原因,答案会更有帮助。
        • 这包括一个尾随分隔符。当然,OP 没有指定,但通常在字段之间放置一个分隔符,而不是在最后一个字段的末尾。
        【解决方案9】:

        更快的变体:

        vector<string> x = {"1", "2", "3"};
        string res;
        res.reserve(16);
        
        std::accumulate(std::begin(x), std::end(x), 0,
                        [&res](int &, string &s)
                        {
                            if (!res.empty())
                            {
                                res.append(",");
                            }
                            res.append(s);
                            return 0;
                       });
        

        它不会创建临时字符串,而只是为整个字符串结果分配一次内存,并将每个 elem 附加到 &res 的末尾

        【讨论】:

        • 您使用的是accumulate(),但完全忽略了该结果。您应该改用std::for_each()
        • 这个函数调用的实际返回值为0(见lambda -> return 0;)。但 'res' 包含结果字符串
        • 你知道accumulate 期望它的功能没有副作用吗?这段代码最好表示为for_each
        猜你喜欢
        • 1970-01-01
        • 2016-06-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-21
        • 2021-08-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多