Convert a vector<int> to a string
我有一个
1 | "1,2,3,4" |
C++中最干净的方法是什么?在python中,我是这样做的:
1 2 3 | >>> array = [1,2,3,4] >>>",".join(map(str,array)) '1,2,3,4' |
绝对不像Python那么优雅,但是没有什么比Python在C++中高雅了。
你可以用一个
1 2 3 4 5 6 7 8 | std::stringstream ss; for(size_t i = 0; i < v.size(); ++i) { if(i != 0) ss <<","; ss << v[i]; } std::string s = ss.str(); |
你也可以用
使用std::copy和std::ostream_迭代器,我们可以得到和python一样优雅的东西。
1 2 3 4 5 6 7 8 9 10 11 12 | #include <iostream> #include <sstream> #include #include <iterator> int main() { int array[] = {1,2,3,4}; std::copy(array, array+4, std::ostream_iterator<int>(std::cout,",")); } |
在我写的一个小课堂上看这个问题。这不会打印尾随逗号。此外,如果我们假设C++ 14将继续给我们这样的算法的基于范围的等价物:
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace std { // I am assuming something like this in the C++14 standard // I have no idea if this is correct but it should be trivial to write if it does not appear. template<typename C, typename I> void copy(C const& container, I outputIter) {copy(begin(container), end(container), outputIter);} } using POI = PrefexOutputIterator; int main() { int array[] = {1,2,3,4}; std::copy(array, POI(std::cout,",")); //",".join(map(str,array)) // closer } |
另一种选择是使用
1 2 3 4 5 6 7 8 | #include <iterator> // ostream_iterator #include <sstream> // ostringstream #include // copy std::ostringstream stream; std::copy(array.begin(), array.end(), std::ostream_iterator<>(stream)); std::string s=stream.str(); s.erase(s.length()-1); |
也不如Python好。为此,我创建了一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | template <class T, class A> T join(const A &begin, const A &end, const T &t) { T result; for (A it=begin; it!=end; it++) { if (!result.empty()) result.append(t); result.append(*it); } return result; } |
然后像这样使用:
1 | std::string s=join(array.begin(), array.end(), std::string(",")); |
您可能会问我为什么传入迭代器。好吧,实际上我想反转数组,所以我这样使用它:
1 | std::string s=join(array.rbegin(), array.rend(), std::string(",")); |
理想情况下,我希望模板输出到可以推断char类型并使用字符串流的位置,但我还不能确定这一点。
您可以使用std::accumulation。考虑下面的例子
1 2 3 4 5 6 | if (v.empty() return std::string(); std::string s = std::accumulate(v.begin()+1, v.end(), std::to_string(v[0]), [](const std::string& a, int b){ return a + ',' + std::to_string(b); }); |
使用Boost和C++ 11,这样可以实现:
1 2 | auto array = {1,2,3,4}; join(array | transformed(tostr),","); |
嗯,差不多。以下是完整的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include #include <iostream> #include <boost/algorithm/string/join.hpp> #include <boost/range/adaptor/transformed.hpp> int main() { using boost::algorithm::join; using boost::adaptors::transformed; auto tostr = static_cast<std::string(*)(int)>(std::to_string); auto array = {1,2,3,4}; std::cout << join(array | transformed(tostr),",") << std::endl; return 0; } |
归功于Praetorian。
您可以处理以下任何值类型:
1 2 3 4 5 6 7 8 9 | template<class Container> std::string join(Container const & container, std::string delimiter) { using boost::algorithm::join; using boost::adaptors::transformed; using value_type = typename Container::value_type; auto tostr = static_cast<std::string(*)(value_type)>(std::to_string); return join(container | transformed(tostr), delimiter); }; |
这仅仅是试图解决1800信息公司关于他的第二个解决方案的评论中给出的谜团,而不是试图回答这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | template <class Str, class It> Str join(It begin, const It end, const Str &sep) { typedef typename Str::value_type char_type; typedef typename Str::traits_type traits_type; typedef typename Str::allocator_type allocator_type; typedef std::basic_ostringstream<char_type,traits_type,allocator_type> ostringstream_type; ostringstream_type result; if(begin!=end) result << *begin++; while(begin!=end) { result << sep; result << *begin++; } return result.str(); } |
在我的机器上工作。
很多模板/想法。我的不是一般的或者高效的,但是我也有同样的问题,我想把它作为一种短而甜的东西投入到混合物中。它在最短的线数上获胜…:)
1 2 3 4 5 6 7 | std::stringstream joinedValues; for (auto value: array) { joinedValues << value <<","; } //Strip off the trailing comma std::string result = joinedValues.str().substr(0,joinedValues.str().size()-1); |
如果你想做
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 | template <typename C, typename T> class MyJoiner { C &c; T &s; MyJoiner(C &&container, T&& sep) : c(std::forward<C>(container)), s(std::forward<T>(sep)) {} public: template<typename C, typename T> friend std::ostream& operator<<(std::ostream &o, MyJoiner<C, T> const &mj); template<typename C, typename T> friend MyJoiner<C, T> join(C &&container, T&& sep); }; template<typename C, typename T> std::ostream& operator<<(std::ostream &o, MyJoiner<C, T> const &mj) { auto i = mj.c.begin(); if (i != mj.c.end()) { o << *i++; while (i != mj.c.end()) { o << mj.s << *i++; } } return o; } template<typename C, typename T> MyJoiner<C, T> join(C &&container, T&& sep) { return MyJoiner<C, T>(std::forward<C>(container), std::forward<T>(sep)); } |
注意,这个解决方案直接将连接连接到输出流中,而不是创建一个辅助缓冲区,并且将与任何类型一起使用,这些类型的运算符<<到一个Ostream上。
当你用
1 2 3 | string s; for (auto i : v) s += (s.empty() ?"" :",") + to_string(i); |
有一些有趣的尝试可以为这个问题提供一个优雅的解决方案。我有一个想法,使用模板流来有效地回答操作的原始困境。虽然这是一篇老文章,但我希望未来偶然发现这篇文章的用户会发现我的解决方案是有益的。
首先,一些答案(包括已接受的答案)不促进重用。由于C++没有提供一种优雅的方法来连接标准库中的字符串(我已经看到),因此创建一个灵活且可重用的字符串变得很重要。这是我的照片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Replace with your namespace // namespace my { // Templated join which can be used on any combination of streams, iterators and base types // template <typename TStream, typename TIter, typename TSeperator> TStream& join(TStream& stream, TIter begin, TIter end, TSeperator seperator) { // A flag which, when true, has next iteration prepend our seperator to the stream // bool sep = false; // Begin iterating through our list // for (TIter i = begin; i != end; ++i) { // If we need to prepend a seperator, do it // if (sep) stream << seperator; // Stream the next value held by our iterator // stream << *i; // Flag that next loops needs a seperator // sep = true; } // As a convenience, we return a reference to the passed stream // return stream; } } |
现在要使用它,您只需执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Load some data // std::vector<int> params; params.push_back(1); params.push_back(2); params.push_back(3); params.push_back(4); // Store and print our results to standard out // std::stringstream param_stream; std::cout << my::join(param_stream, params.begin(), params.end(),",").str() << std::endl; // A quick and dirty way to print directly to standard out // my::join(std::cout, params.begin(), params.end(),",") << std::endl; |
请注意,流的使用如何使该解决方案具有难以置信的灵活性,因为我们可以将结果存储在StringStream中以在稍后回收它,或者我们可以直接写入标准输出、文件,甚至写入作为流实现的网络连接。要打印的类型必须是可迭代的,并且与源流兼容。STL提供了各种类型兼容的流。所以你可以带着这个去城里。在我的头顶上,你的向量可以是int、float、double、string、unsigned int、someobject*等等。
我喜欢1800年的答案。但是,我会将第一个迭代移出循环,因为if语句在第一个迭代之后只更改一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | template <class T, class A> T join(const A &begin, const A &end, const T &t) { T result; A it = begin; if (it != end) { result.append(*it); ++it; } for( ; it!=end; ++it) { result.append(t); result.append(*it); } return result; } |
当然,如果您愿意,可以将其简化为更少的语句:
1 2 3 4 5 6 7 8 9 10 11 12 | template <class T, class A> T join(const A &begin, const A &end, const T &t) { T result; A it = begin; if (it != end) result.append(*it++); for( ; it!=end; ++it) result.append(t).append(*it); return result; } |
我已经创建了一个助手头文件来添加扩展连接支持。
只需将下面的代码添加到常规头文件中,并在需要时包含它。
使用实例:
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 | /* An example for a mapping function. */ ostream& map_numbers(ostream& os, const void* payload, generic_primitive data) { static string names[] = {"Zero","One","Two","Three","Four"}; os << names[data.as_int]; const string* post = reinterpret_cast<const string*>(payload); if (post) { os <<"" << *post; } return os; } int main() { int arr[] = {0,1,2,3,4}; vector<int> vec(arr, arr + 5); cout << vec << endl; /* Outputs: '0 1 2 3 4' */ cout << join(vec.begin(), vec.end()) << endl; /* Outputs: '0 1 2 3 4' */ cout << join(vec.begin(), vec.begin() + 2) << endl; /* Outputs: '0 1 2' */ cout << join(vec.begin(), vec.end(),",") << endl; /* Outputs: '0, 1, 2, 3, 4' */ cout << join(vec.begin(), vec.end(),",", map_numbers) << endl; /* Outputs: 'Zero, One, Two, Three, Four' */ string post ="Mississippi"; cout << join(vec.begin() + 1, vec.end(),",", map_numbers, &post) << endl; /* Outputs: 'One Mississippi, Two mississippi, Three mississippi, Four mississippi' */ return 0; } |
场景背后的代码:
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | #include <iostream> #include <vector> #include <list> #include <set> #include <unordered_set> using namespace std; #define GENERIC_PRIMITIVE_CLASS_BUILDER(T) generic_primitive(const T& v) { value.as_##T = v; } #define GENERIC_PRIMITIVE_TYPE_BUILDER(T) T as_##T; typedef void* ptr; /** A union that could contain a primitive or void*, * used for generic function pointers. * TODO: add more primitive types as needed. */ struct generic_primitive { GENERIC_PRIMITIVE_CLASS_BUILDER(int); GENERIC_PRIMITIVE_CLASS_BUILDER(ptr); union { GENERIC_PRIMITIVE_TYPE_BUILDER(int); GENERIC_PRIMITIVE_TYPE_BUILDER(ptr); }; }; typedef ostream& (*mapping_funct_t)(ostream&, const void*, generic_primitive); template<typename T> class Join { public: Join(const T& begin, const T& end, const string& separator ="", mapping_funct_t mapping = 0, const void* payload = 0): m_begin(begin), m_end(end), m_separator(separator), m_mapping(mapping), m_payload(payload) {} ostream& apply(ostream& os) const { T begin = m_begin; T end = m_end; if (begin != end) if (m_mapping) { m_mapping(os, m_payload, *begin++); } else { os << *begin++; } while (begin != end) { os << m_separator; if (m_mapping) { m_mapping(os, m_payload, *begin++); } else { os << *begin++; } } return os; } private: const T& m_begin; const T& m_end; const string m_separator; const mapping_funct_t m_mapping; const void* m_payload; }; template <typename T> Join<T> join(const T& begin, const T& end, const string& separator ="", ostream& (*mapping)(ostream&, const void*, generic_primitive) = 0, const void* payload = 0) { return Join<T>(begin, end, separator, mapping, payload); } template<typename T> ostream& operator<<(ostream& os, const vector<T>& vec) { return join(vec.begin(), vec.end()).apply(os); } template<typename T> ostream& operator<<(ostream& os, const list<T>& lst) { return join(lst.begin(), lst.end()).apply(os); } template<typename T> ostream& operator<<(ostream& os, const set<T>& s) { return join(s.begin(), s.end()).apply(os); } template<typename T> ostream& operator<<(ostream& os, const Join<T>& vec) { return vec.apply(os); } |
这是一个允许你做的C++ 11解决方案。
1 2 3 4 5 | int main() { vector<int> v {1,2,3}; cout << join(v,",") << endl; string s = join(v, '+').str(); } |
代码是:
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 | template<typename Iterable, typename Sep> class Joiner { const Iterable& i_; const Sep& s_; public: Joiner(const Iterable& i, const Sep& s) : i_(i), s_(s) {} std::string str() const {std::stringstream ss; ss << *this; return ss.str();} template<typename I, typename S> friend std::ostream& operator<< (std::ostream& os, const Joiner<I,S>& j); }; template<typename I, typename S> std::ostream& operator<< (std::ostream& os, const Joiner<I,S>& j) { auto elem = j.i_.begin(); if (elem != j.i_.end()) { os << *elem; ++elem; while (elem != j.i_.end()) { os << j.s_ << *elem; ++elem; } } return os; } template<typename I, typename S> inline Joiner<I,S> join(const I& i, const S& s) {return Joiner<I,S>(i, s);} |
以下是将
1 2 3 4 5 6 7 8 9 10 | std::string join(const std::vector<int>& numbers, const std::string& delimiter =",") { std::ostringstream result; for (const auto number : numbers) { if (result.tellp() > 0) { // not first round result << delimiter; } result << number; } return result.str(); } |
你需要用
我用这种东西
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 | namespace std { // for strings join string to_string( string value ) { return value; } } // namespace std namespace // anonymous { template< typename T > std::string join( const std::vector<T>& values, char delimiter ) { std::string result; for( typename std::vector<T>::size_type idx = 0; idx < values.size(); ++idx ) { if( idx != 0 ) result += delimiter; result += std::to_string( values[idx] ); } return result; } } // namespace anonymous |
我从@sbi的答案开始,但大多数时间都是将生成的字符串通过管道传输到流,因此创建了下面的解决方案,该解决方案可以通过管道传输到流,而无需在内存中创建完整字符串。
其用途如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include"string_join.h" #include <iostream> #include <vector> int main() { std::vector<int> v = { 1, 2, 3, 4 }; // String version std::string str = join(v, std::string(",")); std::cout << str << std::endl; // Directly piped to stream version std::cout << join(v, std::string(",")) << std::endl; } |
其中,string_join.h是:
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 48 49 50 51 52 53 54 55 56 57 58 59 | #pragma once #include <iterator> #include <sstream> template<typename Str, typename It> class joined_strings { private: const It begin, end; Str sep; public: typedef typename Str::value_type char_type; typedef typename Str::traits_type traits_type; typedef typename Str::allocator_type allocator_type; private: typedef std::basic_ostringstream<char_type, traits_type, allocator_type> ostringstream_type; public: joined_strings(It begin, const It end, const Str &sep) : begin(begin), end(end), sep(sep) { } operator Str() const { ostringstream_type result; result << *this; return result.str(); } template<typename ostream_type> friend ostream_type& operator<<( ostream_type &ostr, const joined_strings<Str, It> &joined) { It it = joined.begin; if(it!=joined.end) ostr << *it; for(++it; it!=joined.end; ++it) ostr << joined.sep << *it; return ostr; } }; template<typename Str, typename It> inline joined_strings<Str, It> join(It begin, const It end, const Str &sep) { return joined_strings<Str, It>(begin, end, sep); } template<typename Str, typename Container> inline joined_strings<Str, typename Container::const_iterator> join( Container container, const Str &sep) { return join(container.cbegin(), container.cend(), sep); } |
我写了以下代码。它基于c_string.join。它与std::string和std::wstring以及许多类型的向量一起工作。(注释中的示例)
这样称呼它:
1 2 3 | std::vector<int> vVectorOfIds = {1, 2, 3, 4, 5}; std::wstring wstrStringForSQLIn = Join(vVectorOfIds, L','); |
代码:
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 | // Generic Join template (mimics string.Join() from C#) // Written by RandomGuy (stackoverflow) 09-01-2017 // Based on Brian R. Bondy anwser here: // http://stackoverflow.com/questions/1430757/c-vector-to-string // Works with char, wchar_t, std::string and std::wstring delimiters // Also works with a different types of vectors like ints, floats, longs template<typename T, typename D> auto Join(const std::vector<T> &vToMerge, const D &delimiter) { // We use std::conditional to get the correct type for the stringstream (char or wchar_t) // stringstream = basic_stringstream<char>, wstringstream = basic_stringstream<wchar_t> using strType = std::conditional< std::is_same<D, std::string>::value, char, std::conditional< std::is_same<D, char>::value, char, wchar_t >::type >::type; std::basic_stringstream<strType> ss; for (size_t i = 0; i < vToMerge.size(); ++i) { if (i != 0) ss << delimiter; ss << vToMerge[i]; } return ss.str(); } |
在不限于
1 2 3 4 5 6 7 8 9 | std::vector<int> vec{ 1, 2, 3 }; // Call modern range-based overload. auto str = join( vec, "," ); auto wideStr = join( vec, L"," ); // Call old-school iterator-based overload. auto str = join( vec.begin(), vec.end(), "," ); auto wideStr = join( vec.begin(), vec.end(), L"," ); |
在原始代码中,如果分隔符是字符串文字(如上面的示例中所示),模板参数推导无法生成正确的返回字符串类型。在这种情况下,函数体中的
为了解决这个问题,下面的代码尝试只从分隔符参数中推导字符类型,并使用该类型生成默认的返回字符串类型。这是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <string> #include <sstream> #include <boost/range.hpp> template< class Sep, class Str = std::basic_string< typename boost::range_value< Sep >::type >, class InputIt > Str join( InputIt first, const InputIt last, const Sep& sep ) { using char_type = typename Str::value_type; using traits_type = typename Str::traits_type; using allocator_type = typename Str::allocator_type; using ostringstream_type = std::basic_ostringstream< char_type, traits_type, allocator_type >; ostringstream_type result; if( first != last ) { result << *first++; } while( first != last ) { result << sep << *first++; } return result.str(); } |
现在,我们可以轻松地提供一个基于范围的重载,它只需转发到基于迭代器的重载:
1 2 3 4 5 6 7 8 9 10 11 | template <class Sep, class Str = std::basic_string< typename boost::range_value<Sep>::type >, class InputRange> Str join( const InputRange &input, const Sep &sep ) { // Include the standard begin() and end() in the overload set for ADL. This makes the // function work for standard types (including arrays), aswell as any custom types // that have begin() and end() member functions or overloads of the standalone functions. using std::begin; using std::end; // Call iterator-based overload. return join( begin(input), end(input), sep ); } |
Coliru现场演示
正如@capone所做的,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | std::string join(const std::vector<std::string> &str_list , const std::string &delim="") { if(str_list.size() == 0) return"" ; return std::accumulate( str_list.cbegin() + 1, str_list.cend(), str_list.at(0) , [&delim](const std::string &a , const std::string &b) { return a + delim + b ; } ) ; } template <typename ST , typename TT> std::vector<TT> map(TT (*op)(ST) , const vector<ST> &ori_vec) { vector<TT> rst ; std::transform(ori_vec.cbegin() , ori_vec.cend() , back_inserter(rst) , [&op](const ST& val){ return op(val) ;} ) ; return rst ; } |
然后我们可以这样称呼:
1 2 3 4 5 6 7 | int main(int argc , char *argv[]) { vector<int> int_vec = {1,2,3,4} ; vector<string> str_vec = map<int,string>(to_string, int_vec) ; cout << join(str_vec) << endl ; return 0 ; } |
就像Python一样:
1 | >>>"".join( map(str, [1,2,3,4]) ) |