سؤال

لدي vector<int> الحاوية التي لديها أعداد صحيحة (مثلا ، {1,2,3,4}) وأود أن تحول إلى سلسلة من شكل

"1,2,3,4"

ما هو أنظف طريقة للقيام بذلك في C++?في بيثون هذا هو كيف نفعل ذلك:

>>> array = [1,2,3,4]
>>> ",".join(map(str,array))
'1,2,3,4'
هل كانت مفيدة؟

المحلول

وبالتأكيد ليست أنيقة مثل بيثون، ولكن لا شيء تماما أنيقة مثل بايثون في 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 بدلا من ذلك.

نصائح أخرى

وعن طريق الأمراض المنقولة جنسيا :: for_each وامدا يمكنك أن تفعل شيئا للاهتمام.

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

ويمكنك استخدام الأمراض المنقولة جنسيا :: تتراكم. النظر في المثال التالي

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

وثمة بديل آخر هو استخدام 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);

وأيضا ليست لطيفة مثل بايثون. لهذا الغرض، أنا خلق وظيفة 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(","));

ومن الناحية المثالية، أود أن القالب إلى النقطة التي يمكن أن نستنتج نوع شار، واستخدام سلسلة تيارات، لكني لم أستطع أن بها حتى الآن.

ومع تفعيل و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);
};

والكثير من قالب / الأفكار. الألغام ليست كما عام أو كفاءة، ولكن أنا فقط كان نفس المشكلة وأراد أن يلقي هذا في مزيج كشيء قصيرة وحلوة. أنه يفوز على عدد أقصر من خطوط ...:)

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

ملحوظة، وهذا الحل لا في الانضمام مباشرة إلى دفق إخراج بدلا من إنشاء منطقة عازلة الثانوي وستعمل مع أي أنواع التي لديها عامل << على لostream.

وهذا يعمل أيضا حيث فشل boost::algorithm::join()، عندما يكون لديك vector<char*> بدلا من vector<string>.

وأنا أحب إجابة 1800. ولكن أود أن نقل التكرار الأول من الحلقة مثل نتيجة لبيان إذا يتغير مرة واحدة فقط بعد التكرار الأول

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 الأصلية. وإن كان هذا هو آخر العمر، وأنا على أمل المستخدمين في المستقبل الذين تتعثر هذه سوف تجد لي حلا مفيدا.

أولا، بعض الإجابات (بما في ذلك إجابة مقبولة) لا تشجع على إعادة الاستخدام. منذ 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;

وملاحظة كيفية استخدام تيارات يجعل هذا حل مرن بشكل لا يصدق ونحن يمكن تخزين نتيجة لدينا في stringstream لاستعادتها في وقت لاحق، أو أننا يمكن أن يكتب مباشرة إلى معيار بها، ملف، أو حتى إلى اتصال شبكة تنفيذها باعتبارها مجرى. نوع قيد الطباعة يجب أن يكون ببساطة iteratable ومتوافقة مع تيار المصدر. يوفر STL مختلف التيارات التي تتوافق مع مجموعة واسعة من أنواع. لذلك يمكن أن تذهب حقا إلى المدينة مع هذا. من على قمة رأسي، يمكن أن ناقلات الخاص بك سيكون من كثافة، تطفو، مزدوجة، سلسلة، صحيح غير الموقعة، SomeObject *، وأكثر من ذلك.

string s;
for (auto i : v)
    s += (s.empty() ? "" : ",") + to_string(i);

ولقد إنشاء ملف رأس المساعد لإضافة موسعة الانضمام الدعم.

وفقط بإضافة التعليمة البرمجية أدناه إلى ملف رأس العام وإدراجه عند الحاجة.

وأمثلة الاستخدام:

/* 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.

التوسع في محاولة @الهيئة في عامة الحل هذا لا يقتصر على 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 );
}

يعيش التجريبي في 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

ولقد بدأت مع الإجابة @ الهيئة الفرعية للتنفيذ ولكن في معظم الوقت انتهى الأنابيب السلسلة الناتجة إلى دفق ذلك خلق حل أدناه التي يمكن ضخها إلى تيار دون النفقات العامة من خلق سلسلة كاملة في الذاكرة.

ويتم استخدامه كما يلي:

#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. وهي تعمل مع الأمراض المنقولة جنسيا :: سلسلة والأمراض المنقولة جنسيا :: 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();
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top