Convert a vector<int> to a string
Asked Answered
N

26

111

I have a vector<int> container that has integers (e.g. {1,2,3,4}) and I would like to convert to a string of the form

"1,2,3,4"

What is the cleanest way to do that in C++? In Python this is how I would do it:

>>> array = [1,2,3,4]
>>> ",".join(map(str,array))
'1,2,3,4'
Napalm answered 16/9, 2009 at 3:14 Comment(2)
Closely related: #4850973Edina
In haskell I'd say "show array". ROFL.Geranium
K
112

Definitely not as elegant as Python, but nothing quite is as elegant as Python in C++.

You could use a 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();

You could also make use of std::for_each instead.

Kennel answered 16/9, 2009 at 3:19 Comment(7)
I think you mean array.size() not v.size(), no?Totemism
ya whatever the vector is called.Kennel
Thanks! Now I need to convert ss to char* type (for use with a C function). Any suggestions on how to do that?Napalm
That should be std::string s = ss.str(). If you want a const char*, use s.c_str(). (Note that, while syntactically correct, ss.str().c_str() will give you a const char* that points to a temporary which will will cease to exist at the end of the full expression. That hurts.)Vanish
Thanks sbi: Changed ss.string() to ss.str(). boost path_t has .string() so that's where that came from :)Kennel
why not just use string.append?Engenia
answer is incomplete without #include <sstream>Cement
R
47

Using std::for_each and lambda you can do something interesting.

#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=',';
                   });
}

See this question for a little class I wrote. This will not print the trailing comma. Also if we assume that C++14 will continue to give us range based equivalents of algorithms like this:

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
}
Retinite answered 16/9, 2009 at 4:9 Comment(6)
I think this is not quite equivalent to Python's join - it will insert an extra "," at the end.Gironde
Not equivalent but just as elegant (in fact I think more so but that is just an opinion).Retinite
Obviously elegance is subjective. So if you and two other people prefer longer, more repetitive code that doesn't work, then it's more elegant ;-pVivacious
You can ignore the final comma by using the string::substr member function. Assign the the substring from 0 to n-1 to your result variable.Acquiescent
@SteveJessop: Better?Retinite
@LokiAstari: it's an improvement. Personally I'd probably write an algorithm join rather than a joining OutputIterator, but it amounts to much the same.Vivacious
S
30

You can use std::accumulate. Consider the following example

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);
                     });
Swanhilda answered 31/5, 2015 at 11:6 Comment(2)
',' should be ","Applecart
@Applecart The string class has an overload for + operator that can accept characters too. So ',' is just fine.Comity
G
23

Another alternative is the use of std::copy and the ostream_iterator class:

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

Also not as nice as Python. For this purpose, I created a join function:

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

Then used it like this:

std::string s=join(array.begin(), array.end(), std::string(","));

You might ask why I passed in the iterators. Well, actually I wanted to reverse the array, so I used it like this:

std::string s=join(array.rbegin(), array.rend(), std::string(","));

Ideally, I would like to template out to the point where it can infer the char type, and use string-streams, but I couldn't figure that out yet.

Gironde answered 16/9, 2009 at 4:9 Comment(3)
Since it would be too much for a comment, I have posted an answer (https://mcmap.net/q/194414/-convert-a-vector-lt-int-gt-to-a-string#1432040) which attempts to solve the riddle given in your last sentence.Vanish
Your join function can be used with vectors as well? May you please give example, I'm new to C++.Datolite
Can you change the iterator to a preincrement in the answer?Guide
C
16

With Boost and C++11 this could be achieved like this:

auto array = {1,2,3,4};
join(array | transformed(tostr), ",");

Well, almost. Here's the full example:

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

Credit to Praetorian.

You can handle any value type like this:

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);
};
Cerda answered 6/10, 2015 at 14:25 Comment(0)
V
11

This is just an attempt to solve the riddle given by 1800 INFORMATION's remark on his second solution lacking genericity, not an attempt to answer the question:

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

Works On My Machine(TM).

Vanish answered 16/9, 2009 at 9:50 Comment(5)
Visual Studio 2013 gets very confused by the typedefs. Not that you could have known that in 2009.Thermoplastic
@Jes: I have now been staring at this for 5mins, but couldn't figure out what VS might trip over. Can you be more specific?Vanish
I'm sorry, I think I had attempted a join of objects without << overloads, which I now realize is inappropriate for your code. I cannot cause your code to not compile with a vector of strings. On a side note, VS 2013 Community is both free and feature-ful, unlike "Express" versions.Thermoplastic
@Jes: This should work with any type that can be streamed (i.e., has operator<< overloaded). Of course, a type without operator<< might cause very confusing error messages.Vanish
Unfortunately, this doesn't compile: join(v.begin(), v.end(), ","). Template argument deduction doesn't produce the right result if the sep argument is a string literal. My attempt at a solution for this issue. Also providing a more modern range-based overload.Sweeney
G
7

Lots of template/ideas. Mine's not as generic or efficient, but I just had the same problem and wanted to throw this into the mix as something short and sweet. It wins on shortest number of lines... :)

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);
Godthaab answered 1/5, 2014 at 18:48 Comment(2)
Thanks for the simplistic code. Might want to change it to "auto &" though to avoid the extra copies and get some easy performance gains.Guide
Instead of using substr(...), use pop_back() to remove last character, becomes much more clear and clean then.Ute
O
7
string s;
for (auto i : v)
    s += (s.empty() ? "" : ",") + to_string(i);
Oxytocin answered 23/1, 2019 at 10:17 Comment(2)
Welcome to Stack Overflow! While this code could solve the problem, it is best to add elaboration and explain how it works for people who might not understand this piece of code.Pierro
The current top answer is not much more elaborate, and this is the smallest/cleanest working answer. Not as efficient as std::stringstream for large arrays because stringstream will be able to allocate memory optimistically, leading to O(n.log(n)) performance instead of O(n²) for an array of size n for this answer. Also stringstream might not build temporary strings for to_string(i).Brouhaha
A
4

If you want to do std::cout << join(myVector, ",") << std::endl;, you can do something like:

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

Note, this solution does the join directly into the output stream rather than creating a secondary buffer and will work with any types that have an operator<< onto an ostream.

This also works where boost::algorithm::join() fails, when you have a vector<char*> instead of a vector<string>.

Adkisson answered 21/6, 2015 at 22:59 Comment(0)
M
2

I like 1800's answer. However I would move the first iteration out of the loop as as the result of the if statement only changes once after the first iteration

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

This can of course be reduced down to fewer statements if you like:

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;
}
Maintenon answered 16/9, 2009 at 10:23 Comment(5)
You shouldn't use post-increment for unknown iterator types. That might be expensive. (Of course, when dealing with strings, that might not make that much of a difference. But once you learn the habit...)Vanish
Post increment is fine as long as you use the tempory value that is returned. eg "result.append(*it); ++it;" is almost always as expensive as "result.append(*it++);" the second has one extra copy of the iterator.Maintenon
Oops I just spotted the post increment in the for loop. copy and paste error. I have fixed the post.Maintenon
@Ian: When I taught C++, I hammered into my students to use ++i except where they really needed i++ because that was the only way they wouldn't forget this when it made a difference. (It was the same with me, BTW.) They had learned Java before, where all kinds of C-isms are en vogue and it took them a few months (1 lecture +lab work per week), but in the end most of them learned the habit to use pre-increment.Vanish
@sbi: agreed I always default to preincrement too, the rogue postincrement came from copying someone elses for loop and changing it. In my first reply I thought you were worried about "result.append(*it++)" and not the for loop. I was a little embarrassed to see the post increment in the loop. Some people seem to follow the advice of not using post increment too far and never use it or change it even when it is appropriate. However I know now you don't fall in to this category.Maintenon
J
2

There are some interesting attempts at providing an elegant solution to the problem. I had an idea to use templated streams to effectively answer the OP's original dilemma. Though this is an old post, I'm hoping future users who stumble upon this will find my solution beneficial.

First, some answers (including the accepted answer) do not promote re-usability. Since C++ doesn't provide an elegant way to join strings in the standard library (that I have seen), it becomes important to create one that is flexible and reusable. Here's my shot at it:

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

Now to use this, you could simply do something like the following:

// 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;

Note how the use of streams makes this solution incredibly flexible as we can store our result in a stringstream to reclaim it later, or we can write directly to the standard out, a file, or even to a network connection implemented as a stream. The type being printed must simply be iteratable and compatible with the source stream. STL provides various streams which are compatible with a large range of types. So you could really go to town with this. Off the top of my head, your vector can be of int, float, double, string, unsigned int, SomeObject*, and more.

Jowl answered 13/7, 2013 at 19:8 Comment(0)
P
2

why answers here are so ridiculously complex

string vec2str( vector<int> v){
        string s="";
        for (auto e: v){
            s+=to_string(e);
            s+=',';
        }
        s.pop_back();
        return s;
   }
Portiaportico answered 30/4, 2021 at 6:51 Comment(0)
G
1

I've created an helper header file to add an extended join support.

Just add the code below to your general header file and include it when needed.

Usage Examples:

/* 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;
}

The code behind the scene:

#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);
}
Guideline answered 28/11, 2016 at 18:54 Comment(0)
P
1

Here's a generic C++11 solution that will let you do

int main() {
    vector<int> v {1,2,3};
    cout << join(v, ", ") << endl;
    string s = join(v, '+').str();
}

The code is:

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);}
Pyrope answered 10/4, 2017 at 0:5 Comment(0)
B
1

The following is a simple and practical way to convert elements in a vector to a 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();
}

You need to #include <sstream> for ostringstream.

Bristol answered 11/6, 2018 at 21:11 Comment(0)
S
1

Expanding on the attempt of @sbi at a generic solution that is not restricted to std::vector<int> or a specific return string type. The code presented below can be used like this:

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"," );

In the original code, template argument deduction does not work to produce the right return string type if the separator is a string literal (as in the samples above). In this case, the typedefs like Str::value_type in the function body are incorrect. The code assumes that Str is always a type like std::basic_string, so it obviously fails for string literals.

To fix this, the following code tries to deduce only the character type from the separator argument and uses that to produce a default return string type. This is achieved using boost::range_value, which extracts the element type from the given range type.

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

Now we can easily provide a range-based overload that simply forwards to the iterator-based overload:

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

Live Demo at Coliru

Sweeney answered 26/9, 2018 at 19:49 Comment(0)
E
0

as @capone did ,

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

Then we can call like following :

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

just like python :

>>> " ".join( map(str, [1,2,3,4]) )
Elman answered 28/5, 2016 at 14:55 Comment(0)
J
0

I use something like this

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
Junina answered 7/6, 2016 at 6:41 Comment(0)
S
0

I started out with @sbi's answer but most of the time ended up piping the resulting string to a stream so created the below solution that can be piped to a stream without the overhead of creating the full string in memory.

It is used as follows:

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

Where string_join.h is:

#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);
}
Stairhead answered 26/1, 2017 at 11:42 Comment(0)
T
0

I have wrote the following code. It is based in C# string.join. It works with std::string and std::wstring and many type of vectors. (examples in comments)

Call it like this:

 std::vector<int> vVectorOfIds = {1, 2, 3, 4, 5};

 std::wstring wstrStringForSQLIn = Join(vVectorOfIds, L',');

Code:

// Generic Join template (mimics string.Join() from C#)
// Written by RandomGuy (stackoverflow) 09-01-2017
// Based on Brian R. Bondy anwser here:
// https://mcmap.net/q/194414/-convert-a-vector-lt-int-gt-to-a-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();
}
Tosch answered 10/8, 2017 at 11:46 Comment(0)
D
0

Here is an easy way to convert a vector of integers to strings.

#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;
}
Distribute answered 3/4, 2020 at 13:30 Comment(0)
M
0

join using template function

I used a template function to join the vector items, and removed the unnecessary if statement by iterating through only the first to penultimate items in the vector, then joining the last item after the for loop. This also obviates the need for extra code to remove the extra separator at the end of the joined string. So, no if statements slowing down the iteration and no superfluous separator that needs tidying up.

This produces an elegant function call to join a vector of string, integer, or double, etc.

I wrote two versions: one returns a string; the other writes directly to a stream.

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

Output

Joined... from... beginning... to... end

1, 2, 3, 4, 5, 6, 7, 8, 9, 10

1.2; 3.4; 5.6; 7.8; 9

Joined... from... beginning... to... end

1, 2, 3, 4, 5, 6, 7, 8, 9, 10

1.2; 3.4; 5.6; 7.8; 9
Modular answered 12/5, 2020 at 22:37 Comment(0)
C
0
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v{{1,2,3,4}};
    std::string str;

    // ----->
    if (! v.empty())
    {
        str = std::to_string(*v.begin());
        for (auto it = std::next(v.begin()); it != v.end(); ++it)
            str.append("," + std::to_string(*it));
    }
    // <-----
    
    std::cout << str << "\n";
}
Cogan answered 6/12, 2020 at 8:3 Comment(0)
M
0

Yet another way to solve this using std::accumulate from numeric library (#include <numeric>):

std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto comma_fold = [](std::string a, int b) { return std::move(a) + ',' + std::to_string(b); };
std::string s = std::accumulate(std::next(v.begin()), v.end(),
                                std::to_string(v[0]), // start with first element
                                comma_fold);
std::cout << s << std::endl; // 1,2,3,4,5,6,7,8,9,10
Marybelle answered 23/7, 2021 at 14:12 Comment(0)
A
0

One complete example with fmt library:

fmtlib/fmt

#include <cstdlib>
#include <vector>
#include <numeric>
#include <string>
#include <fmt/format.h>
#include <iostream>

int main() {
    std::vector<int> vec{1, 2, 3, 4, 5};
    auto joinedText = std::accumulate(vec.begin(), vec.end(), std::string(), [](const std::string &text, int num) {
        return text.empty() ? std::to_string(num) : fmt::format("{},{}", text, std::to_string(num));
    });
    std::cout << joinedText << std::endl;
    return EXIT_SUCCESS;
}

Output

1,2,3,4,5
Appall answered 13/7, 2023 at 8:36 Comment(0)
B
-1

Simple solution/hack... not elegant but it works

const auto vecToString = [](std::vector<int> input_vector)
{
    std::string holder = "";

    for (auto s : input_vector){
        holder += std::to_string(s);
        if(input_vector.back() != s){
            holder += ", ";
        }
    }

    return holder;
};
Bedeck answered 29/3, 2022 at 20:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.