У меня есть 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'
Ответы:
Определенно не такой элегантный, как Python, но нет ничего более элегантного, чем Python в C ++.
Вы могли бы использовать
stringstream
...#include <sstream> //... 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::string s = ss.str()
. Если вы хотитеconst char*
, используйтеs.c_str()
. (Обратите внимание, что, хотя синтаксически верен,ss.str().c_str()
он даст вам значениеconst char*
, указывающее на временное, которое перестанет существовать в конце полного выражения. Это больно.)#include <sstream>
Используя std :: for_each и lambda, вы можете сделать кое-что интересное.
#include <iostream> #include <sstream> int main() { int array[] = {1,2,3,4}; std::for_each(std::begin(array), std::end(array), [&std::cout, sep=' '](int x) mutable { out << sep << x; sep=','; }); }
См. Этот вопрос для небольшого класса, который я написал. Это не приведет к печати конечной запятой. Также, если мы предположим, что 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 :: accumulate. Рассмотрим следующий пример
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); });
источник
','
должно быть","
string
классе есть перегрузка для+
оператора, который также может принимать символы. Так','
что все в порядке.Другой альтернативой является использование
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 и использовать строковые потоки, но я еще не мог этого понять.
источник
join
функция также может использоваться с векторами? Приведите пример, я новичок в C ++.С 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).
источник
operator<<
перегрузился). Конечно, тип безoperator<<
может вызвать очень запутанные сообщения об ошибках.join(v.begin(), v.end(), ",")
. Вывод аргумента шаблона не дает правильного результата, еслиsep
аргумент является строковым литералом. Моя попытка решения этой проблемы . Также обеспечивает более современную перегрузку на основе диапазона.Множество шаблонов / идей. Мой не такой общий или эффективный, но у меня была такая же проблема, и я хотел добавить это в микс как что-то короткое и приятное. Он выигрывает по кратчайшему количеству строк ... :)
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);
источник
substr(...)
используйтеpop_back()
для удаления последнего символа, тогда он станет более четким и чистым.Если хотите
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>
.источник
string s; for (auto i : v) s += (s.empty() ? "" : ",") + to_string(i);
источник
std::stringstream
для больших массивов, потомуstringstream
что сможет оптимистично распределять память, что приведет к производительности O (n.log (n)) вместо O (n²) для массива размераn
для этого ответа. Такжеstringstream
не может создавать временные строки дляto_string(i)
.Мне нравится ответ 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; }
источник
++i
им действительно нужно,i++
потому что это был единственный способ не забыть об этом, когда это имело значение. (То же самое было со мной, кстати.) Они уже изучали Java, где все виды C-измов в моде, и это заняло у них несколько месяцев (1 лекция + лабораторная работа в неделю), но в конце концов большая часть они научились использовать предварительный инкремент.Есть несколько интересных попыток найти элегантное решение проблемы. У меня возникла идея использовать шаблонные потоки, чтобы эффективно решить исходную дилемму OP. Хотя это старый пост, я надеюсь, что будущие пользователи, которые наткнутся на него, сочтут мое решение полезным.
Во-первых, некоторые ответы (включая принятый) не способствуют повторному использованию. Поскольку C ++ не предоставляет элегантного способа соединения строк в стандартной библиотеке (что я видел), становится важным создать такую гибкую и многократно используемую. Вот мой шанс:
// 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;
Обратите внимание, как использование потоков делает это решение невероятно гибким, поскольку мы можем сохранить наш результат в строковом потоке, чтобы восстановить его позже, или мы можем записать непосредственно в стандартный выход, файл или даже в сетевое соединение, реализованное как поток. Печатаемый тип должен быть просто повторяемым и совместимым с исходным потоком. 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);}
источник
Ниже приводится простой и практичный способ преобразования элементов из a
vector
в astring
: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
.источник
Расширение попытки @sbi в универсальном решении , которое не ограничивается
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"," );
В исходном коде вывод аргументов шаблона не работает для получения правильного типа возвращаемой строки, если разделитель является строковым литералом (как в примерах выше). В этом случае определения типов, как
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 ); }
Живая демонстрация на Coliru
источник
как это сделал @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 ; }
как питон:
>>> " ".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.h:
#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 и многими типами векторов. (примеры в комментариях)
Назовите это так:
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(); }
источник
Вот простой способ преобразовать вектор целых чисел в строки.
#include <bits/stdc++.h> using namespace std; int main() { vector<int> A = {1, 2, 3, 4}; string s = ""; for (int i = 0; i < A.size(); i++) { s = s + to_string(A[i]) + ","; } s = s.substr(0, s.length() - 1); //Remove last character cout << s; }
источник
присоединиться с помощью функции шаблона
Я использовал a
template
function
для соединенияvector
элементов и удалил ненужныйif
оператор, перебирая только от первого до предпоследнего элементов вvector
, а затем присоединяясь к последнему элементу послеfor
цикла. Это также устраняет необходимость в дополнительном коде для удаления лишнего разделителя в конце объединенной строки. Итак, никакихif
инструкций, замедляющих итерацию, и никакого лишнего разделителя, который нужно убирать.Это производит вызов функции элегантного , чтобы присоединиться к
vector
оstring
,integer
илиdouble
и т.д.Я написал две версии: одна возвращает строку; другой записывает прямо в поток.
#include <iostream> #include <sstream> #include <string> #include <vector> using namespace std; // Return a string of joined vector items. template<typename T> string join(const vector<T>& v, const string& sep) { ostringstream oss; const auto LAST = v.end() - 1; // Iterate through the first to penultimate items appending the separator. for (typename vector<T>::const_iterator p = v.begin(); p != LAST; ++p) { oss << *p << sep; } // Join the last item without a separator. oss << *LAST; return oss.str(); } // Write joined vector items directly to a stream. template<typename T> void join(const vector<T>& v, const string& sep, ostream& os) { const auto LAST = v.end() - 1; // Iterate through the first to penultimate items appending the separator. for (typename vector<T>::const_iterator p = v.begin(); p != LAST; ++p) { os << *p << sep; } // Join the last item without a separator. os << *LAST; } int main() { vector<string> strings { "Joined", "from", "beginning", "to", "end" }; vector<int> integers{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; vector<double> doubles{ 1.2, 3.4, 5.6, 7.8, 9.0 }; cout << join(strings, "... ") << endl << endl; cout << join(integers, ", ") << endl << endl; cout << join(doubles, "; ") << endl << endl; join(strings, "... ", cout); cout << endl << endl; join(integers, ", ", cout); cout << endl << endl; join(doubles, "; ", cout); cout << endl << endl; return 0; }
Выход
источник