Преобразование вектора в строку
у меня есть vector<int> контейнер, который имеет целых числа (например, {1,2,3,4}) и я хотел бы преобразовать в строку вида
"1,2,3,4"
что такое чистый способ сделать это в C++?
В Python вот как я бы это сделал:
>>> array = [1,2,3,4]
>>> ",".join(map(str,array))
'1,2,3,4'
18 ответов:
определенно не так элегантно, как Python, но ничто не так элегантно, как Python в C++.
вы могли бы использовать
stringstream...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::for_eachвместо.
С помощью std::copy и std:: ostream_iterator мы можем получить что-то столь же элегантное, как python.
#include <iostream> #include <sstream> #include <algorithm> #include <iterator> int main() { int array[] = {1,2,3,4}; std::copy(array, array+4, std::ostream_iterator<int>(std::cout,",")); }посмотреть этот вопрос для небольшого класса я уже писал. Это не будет печатать конечную запятую. Также, если мы предположим, что C++14 будет продолжать давать нам эквиваленты алгоритмов на основе диапазона, такие как:
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 }
Другой альтернативой является использование
std::copyиostream_iteratorкласс:#include <iterator> // ostream_iterator #include <sstream> // ostringstream #include <algorithm> // 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. Для этой цели я создал
joinфункция: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; }затем использовал его так:
std::string s=join(array.begin(), array.end(), std::string(","));вы можете спросить, почему я прошел в итераторы. Ну, на самом деле я хотел изменить массив, поэтому я использовал его так:
std::string s=join(array.rbegin(), array.rend(), std::string(","));в идеале, я хотел бы шаблон до точки, где он может вывести тип char, и использовать струнные потоки, но я еще не мог это понять.
вы можете использовать std::накапливать. Рассмотрим следующий пример
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 это может быть достигнуто следующим образом:
auto array = {1,2,3,4}; join(array | transformed(tostr), ",");ну, почти. Вот полный пример:
#include <array> #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; }кредит преторианской.
вы можете обрабатывать любой тип значения, как это:
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 замечание информации на его второе решение не хватает обобщенности, а не попытки ответить на вопрос:
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(); }работает на моей машине (TM).
много шаблонов / идей. Мой не такой общий или эффективный, но у меня была такая же проблема, и я хотел бросить это в смесь как что-то короткое и сладкое. Он выигрывает на самом коротком количестве линий... :)
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);
если вы хотите сделать
std::cout << join(myVector, ",") << std::endl;, вы можете сделать что-то вроде: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)); }обратите внимание, что это решение выполняет соединение непосредственно в выходной поток, а не создает вторичный буфер и будет работать с любыми типами, которые имеют оператор
Это также работает, где
boost::algorithm::join()завершается, когда у вас естьvector<char*>вместоvector<string>.
мне нравится ответ 1800-х годов. Однако я бы переместил первую итерацию из цикла, поскольку в результате оператор if изменяется только один раз после первой итерации
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; }Это, конечно, может быть уменьшено до меньшего количества утверждений, если вам нравится:
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; }
есть несколько интересных попыток обеспечить элегантное решение проблемы. У меня была идея использовать шаблонные потоки, чтобы эффективно ответить на оригинальную дилемму OP. Хотя это старый пост, я надеюсь, что будущие пользователи, которые наткнутся на это, найдут мое решение полезным.
// 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; } }теперь, чтобы использовать это, вы могли бы просто сделать что-то вроде следующего:
// 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, чтобы восстановить его позже, или мы можем записать непосредственно в стандартный out, файл или даже в сетевое соединение, реализованное как поток. Печатаемый тип должен быть просто итерационный и совместимый с исходным потоком. STL предоставляет различные потоки, которые совместимы с большим диапазоном типов. Так что ты действительно можешь поехать в город с этим. В верхней части моей головы ваш вектор может быть int, float, double, string, unsigned int, SomeObject* и многое другое.
Я создал вспомогательный файл заголовка, чтобы добавить расширенную поддержку соединения.
просто добавьте приведенный ниже код в общий файл заголовка и включите его при необходимости.
Примеры Использования:
/* 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; }код за кадром:
#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, которое позволит вам сделать
int main() { vector<int> v {1,2,3}; cout << join(v, ", ") << endl; string s = join(v, '+').str(); }код:
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);}
ниже приведен простой и практичный способ преобразования элементов в
vectorдоstring: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(); }вам нужно
#include <sstream>наostringstream.
как и @capone,
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 ; }тогда мы можем позвонить следующим образом:
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:
>>> " ".join( map(str, [1,2,3,4]) )
Я использую что-то вроде этого
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, но большую часть времени заканчивал тем, что передавал полученную строку в поток, поэтому создал следующее решение, которое можно передать в поток без накладных расходов на создание полной строки в памяти.
Он используется следующим образом:
#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.ч:
#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#.присоединяться. Он работает с std::string и std:: wstring и многими типами векторов. (примеры в комментариях)
назовем это так:
std::vector<int> vVectorOfIds = {1, 2, 3, 4, 5}; std::wstring wstrStringForSQLIn = Join(vVectorOfIds, L',');код:
// 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(); }
расширение на попытку @sbi в A универсального решения это не ограничивается
std::vector<int>или определенный тип возвращаемой строки. Код, представленный ниже, может быть использован следующим образом: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"," );в исходном коде вычитание аргумента шаблона не работает для получения правильного типа возвращаемой строки, если разделитель является строковым литералом (как в примерах выше). В этом случае, typedefs как
Str::value_typeв теле функции неверны. В коде предполагается, чтоStr- это всегда типаstd::basic_string, поэтому он явно не подходит для строковых литералов.чтобы исправить это, следующий код пытается вывести только символ введите из аргумента разделителя и использует его для создания типа возвращаемой строки по умолчанию. Это достигается с помощью
boost::range_value, который извлекает тип элемента из указанного ряд тип.#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(); }теперь мы можем легко обеспечить перегрузку на основе диапазона, которая просто вперед к перегрузке на основе итератора:
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 ); }
Comments