std::矢量到带有自定义分隔符的字符串



我想将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<int>(s,&delimiter)但我在字符串中得到奇怪的字符。

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

使用 delimiter.c_str() 作为分隔符:

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

这样,您将获得指向字符串的const char*,这是ostream_operator对您的std::string的期望。

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;
                                });

另一种方法:

#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 循环和"自动"(

这是对上面已经提供的两个答案的扩展,因为运行时性能似乎是评论中的一个主题。我会将其添加为评论,但我还没有该特权。

我使用 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 毫秒,在 256 个元素向量上进行 1000 次迭代时总运行时间 (~180ms( 的 ~10-30%(。但是,只有当通过引用传递到 lambda 函数的a参数时,accumulate实现才会更快。按值传递 a 参数会导致类似的运行时差异,有利于stringstream实现。当直接返回结果字符串而不是分配给立即返回的局部变量时,accumulate实现也进行了一些改进。YMMV 与其他C++编译器。

使用 accumulate 调试版本的速度慢了 5-10 倍,因此我认为优化器解决了上面几条评论中提到的额外字符串创建。

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

#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;
}
std::string Gesture::getLabeledPointsString(const std::string delimiter) {
  return boost::join(getLabeledPoints(), delimiter);
}

在这一点上,我不太相信介绍getLabeledPointsString;)

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(
我知道

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

我的要求是:

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

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

#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)

绝对不需要超级花哨的东西。像这样简单的东西会达到相同的结果。

int vectorCounter=0;
//this is where you loop over the contents of your vector
for (auto it = StrVec.begin(); it != StrVec.end(); ++it){
                
    vectorCounter++;
    
    //this print contents of the vector
    cout << *it;
    //this will put a custom delimiter
    if (vectorCounter < StrVec.size())
    {
        //This is where you define your delimiter
        cout << ",";
    }
}//end-for-loop

OUTPUT:
1,2,3,4

如果 ABSL 的使用没问题,你可以这样做:

std::vector<std::string> strings{"h", "e", "ll", "o"};
auto joined(absl::StrJoin(strings, "-"));

见神电

Boost有一个类似的东西,就像@StefanQ建议

的那样

另一个潜在的选项是std::experimental::ostream_joiner

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <vector>
int main() {
    std::vector<int> i{1, 2, 3, 4, 5};
    std::copy(i.begin(), i.end(), 
              std::experimental::make_ostream_joiner(std::cout, ", "));
    return 0;
}

输出:

1, 2, 3, 4, 5

注意:正如 experimental 命名空间所暗示的那样,这尚未标准化,目前在某些编译器上不可用。 例如,我能够在GCC和Clang中使用它,但不能在MSVC中使用它。

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;

更快的变体:

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 的末尾

最新更新