Convertir un vector <int> en una cadena

92

Tengo un vector<int>contenedor que tiene números enteros (por ejemplo, {1,2,3,4}) y me gustaría convertir a una cadena de la forma

"1,2,3,4"

¿Cuál es la forma más limpia de hacer eso en C ++? En Python así es como lo haría:

>>> array = [1,2,3,4]
>>> ",".join(map(str,array))
'1,2,3,4'
DR
fuente
1
Estrechamente relacionado: stackoverflow.com/questions/4850473/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

95

Definitivamente no es tan elegante como Python, pero nada es tan elegante como Python en C ++.

Podrías usar un 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();

También puede utilizar en su std::for_eachlugar.

Brian R. Bondy
fuente
Creo que te refieres a array.size () no v.size (), ¿no?
Mark Elliot
1
ya como se llame el vector.
Brian R. Bondy
2
Eso debería ser std::string s = ss.str(). Si quieres const char*, usa s.c_str(). (Tenga en cuenta que, si bien es sintácticamente correcto, ss.str().c_str()le dará un const char*que apunta a un temporal que dejará de existir al final de la expresión completa. Eso duele.)
sbi
1
¿Por qué no usar string.append?
Baiyan Huang
12
la respuesta es incompleta sin#include <sstream>
renadeen
43

Usando std :: for_each y lambda puedes hacer algo interesante.

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

Vea esta pregunta para una pequeña clase que escribí. Esto no imprimirá la coma final. Además, si asumimos que C ++ 14 continuará dándonos equivalentes basados ​​en rangos de algoritmos como este:

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
}
Martin York
fuente
12
Creo que esto no es del todo equivalente a la combinación de Python: insertará un "," adicional al final.
1800 INFORMACIÓN
2
No equivalente pero igual de elegante (de hecho creo que más pero eso es solo una opinión).
Martin York
20
Evidentemente, la elegancia es subjetiva. Entonces, si usted y otras dos personas prefieren un código más largo y repetitivo que no funciona, entonces es más elegante ;-p
Steve Jessop
1
Puede ignorar la coma final usando la función miembro string :: substr. Asigne la subcadena de 0 a n-1 a su variable de resultado.
Dan
@SteveJessop: ¿Mejor?
Martin York
24

Puede usar std :: acumular. Considere el siguiente ejemplo

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);
                     });
capone
fuente
','debería ser","
Matt
2
@Matt La stringclase tiene una sobrecarga para el +operador que también puede aceptar caracteres. Entonces ','está bien.
Pavan Manjunath
19

Otra alternativa es el uso de std::copyy la ostream_iteratorclase:

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

Tampoco es tan agradable como Python. Para este propósito, creé una joinfunción:

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

Luego lo usó así:

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

Podrías preguntar por qué pasé los iteradores. Bueno, en realidad quería revertir la matriz, así que la usé así:

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

Idealmente, me gustaría hacer una plantilla hasta el punto en que pueda inferir el tipo de char y usar cadenas de secuencias, pero aún no pude entenderlo.

1800 INFORMACIÓN
fuente
Como sería demasiado para un comentario, he publicado una respuesta ( stackoverflow.com/questions/1430757/1432040#1432040 ) que intenta resolver el acertijo dado en su última oración.
sbi
¿Tu joinfunción también se puede utilizar con vectores? ¿Podría dar un ejemplo? Soy nuevo en C ++.
Noitidart
¿Puede cambiar el iterador a un preincremento en la respuesta?
Millie Smith
14

Con Boost y C ++ 11 esto podría lograrse así:

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

Bueno, casi. Aquí está el ejemplo completo:

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

Crédito a Praetorian .

Puede manejar cualquier tipo de valor como este:

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);
};
arekolek
fuente
11

Esto es solo un intento de resolver el acertijo dado por el comentario de 1800 INFORMATION sobre su segunda solución que carece de genérico, no un intento de responder la pregunta:

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

Funciona en mi máquina (TM).

sbi
fuente
Visual Studio 2013 se confunde mucho con typedefs. No es que pudieras haber sabido eso en 2009.
Grault
@Jes: He estado mirando esto durante 5 minutos, pero no pude entender con qué VS podría tropezar. ¿Puedes ser mas específico?
sbi
Lo siento, creo que había intentado unir objetos sin << sobrecargas, que ahora me doy cuenta de que no es apropiado para su código. No puedo hacer que su código no se compile con un vector de cadenas. En una nota al margen, VS 2013 Community es gratuito y lleno de funciones, a diferencia de las versiones "Express".
Grault
@Jes: Esto debería funcionar con cualquier tipo que se pueda transmitir (es decir, que se haya operator<<sobrecargado). Por supuesto, un tipo sin operator<<puede causar mensajes de error muy confusos.
sbi
Desafortunadamente, esto no se compila: join(v.begin(), v.end(), ","). La deducción del argumento de plantilla no produce el resultado correcto si el separgumento es un literal de cadena. Mi intento de solucionar este problema . También proporciona una sobrecarga basada en rango más moderna.
zett42
7

Muchas plantillas / ideas. El mío no es tan genérico o eficiente, pero simplemente tuve el mismo problema y quería incluir esto en la mezcla como algo corto y dulce. Gana en el menor número de líneas ... :)

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);
Joe Schneider
fuente
1
Gracias por el código simplista. Sin embargo, es posible que desee cambiarlo a "automático &" para evitar las copias adicionales y obtener algunas ganancias de rendimiento fáciles.
Millie Smith
En lugar de usar substr(...), use pop_back()para eliminar el último carácter, se vuelve mucho más claro y limpio.
ifyalciner
4

Si quieres hacerlo std::cout << join(myVector, ",") << std::endl;, puedes hacer algo como:

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

Tenga en cuenta que esta solución realiza la unión directamente en el flujo de salida en lugar de crear un búfer secundario y funcionará con cualquier tipo que tenga un operador << en un ostream.

Esto también funciona donde boost::algorithm::join()falla, cuando tiene un en vector<char*>lugar de un vector<string>.

mheyman
fuente
4
string s;
for (auto i : v)
    s += (s.empty() ? "" : ",") + to_string(i);
chenfy27
fuente
7
¡Bienvenido a Stack Overflow! Si bien este código podría resolver el problema, es mejor agregar elaboración y explicar cómo funciona para las personas que quizás no entiendan este código.
paper1111
1
La respuesta principal actual no es mucho más elaborada, y esta es la respuesta de trabajo más pequeña / limpia. No es tan eficiente como std::stringstreampara matrices grandes porque stringstreampodrá asignar memoria de manera optimista, lo que conducirá a un rendimiento de O (n.log (n)) en lugar de O (n²) para una matriz de tamaño npara esta respuesta. Además, es stringstreamposible que no cree cadenas temporales para to_string(i).
aberaud
2

Me gusta la respuesta de 1800. Sin embargo, movería la primera iteración fuera del ciclo como resultado de la instrucción if solo cambia una vez después de la primera iteración

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

Por supuesto, esto se puede reducir a menos declaraciones si lo desea:

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;
}
iain
fuente
No debe utilizar el incremento posterior para tipos de iteradores desconocidos. Eso puede resultar caro. (Por supuesto, cuando se trata de cadenas, eso podría no hacer mucha diferencia. Pero una vez que aprendes el hábito ...)
sbi
El incremento de publicaciones está bien siempre que use el valor temporal que se devuelve. ej. "resultado.append (* it); ++ it;" es casi siempre tan caro como "result.append (* it ++);" el segundo tiene una copia adicional del iterador.
Iain
Vaya, acabo de ver el incremento de publicación en el bucle for. copiar y pegar error. He arreglado la publicación.
Iain
1
@Ian: Cuando enseñé C ++, presioné a mis alumnos para que lo usaran ++iexcepto donde realmente lo necesitaban i++porque esa era la única forma en que no olvidaban esto cuando marcaba la diferencia. (A mí me pasó lo mismo, por cierto). Habían aprendido Java antes, donde todo tipo de C-ismos están de moda y les tomó unos meses (1 conferencia + trabajo de laboratorio por semana), pero al final la mayoría de aprendieron el hábito de utilizar el preincremento.
sbi
1
@sbi: de acuerdo en que siempre prefiero el preincremento también, el postincremento deshonesto vino de copiar a alguien más para el bucle y cambiarlo. En mi primera respuesta, pensé que estabas preocupado por "result.append (* it ++)" y no por el ciclo for. Me avergoncé un poco ver el incremento de publicaciones en el bucle. Algunas personas parecen seguir el consejo de no usar el incremento posterior demasiado y nunca usarlo ni cambiarlo, incluso cuando sea apropiado. Sin embargo, ahora sé que no caes en esta categoría.
Iain
2

Hay algunos intentos interesantes de proporcionar una solución elegante al problema. Tuve la idea de usar secuencias con plantillas para responder de manera efectiva al dilema original del OP. Aunque esta es una publicación antigua, espero que los futuros usuarios que se encuentren con esto encuentren que mi solución es beneficiosa.

Primero, algunas respuestas (incluida la respuesta aceptada) no promueven la reutilización. Dado que C ++ no proporciona una forma elegante de unir cadenas en la biblioteca estándar (que he visto), es importante crear una que sea flexible y reutilizable. Aquí está mi oportunidad:

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

Ahora, para usar esto, simplemente podría hacer algo como lo siguiente:

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

Observe cómo el uso de flujos hace que esta solución sea increíblemente flexible, ya que podemos almacenar nuestro resultado en un flujo de cadenas para recuperarlo más tarde, o podemos escribir directamente en la salida estándar, un archivo o incluso en una conexión de red implementada como flujo. El tipo que se imprime debe ser simplemente iterable y compatible con el flujo de origen. STL proporciona varios flujos que son compatibles con una amplia gama de tipos. Así que realmente podrías ir a la ciudad con esto. En la parte superior de mi cabeza, su vector puede ser de int, float, double, string, unsigned int, SomeObject * y más.

David Peterson
fuente
1

Creé un archivo de encabezado auxiliar para agregar un soporte de unión extendido.

Simplemente agregue el código a continuación a su archivo de encabezado general e inclúyalo cuando sea necesario.

Ejemplos de uso:

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

El código detrás de escena:

#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);
}
Maor Gaon
fuente
1

Aquí hay una solución genérica de C ++ 11 que le permitirá hacer

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

El codigo es:

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);}
n.caillou
fuente
1

La siguiente es una forma sencilla y práctica de convertir elementos de a en vectora 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();
}

Necesitas #include <sstream>para ostringstream.

mrts
fuente
1

Ampliando el intento de @sbi en una solución genérica que no está restringida std::vector<int>o un tipo de cadena de retorno específico. El código que se presenta a continuación se puede utilizar así:

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

En el código original, la deducción del argumento de la plantilla no funciona para producir el tipo de cadena de retorno correcto si el separador es un literal de cadena (como en los ejemplos anteriores). En este caso, las definiciones de tipo como Str::value_typeen el cuerpo de la función son incorrectas. El código asume que Strsiempre es un tipo comostd::basic_string , por lo que obviamente falla para los literales de cadena.

Para solucionar este problema, el siguiente código intenta deducir solo el tipo de carácter del argumento separador y lo usa para producir un tipo de cadena de retorno predeterminado. Esto se logra usando boost::range_value, que extrae el tipo de elemento del tipo de rango dado .

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

Ahora podemos proporcionar fácilmente una sobrecarga basada en rango que simplemente se reenvía a la sobrecarga basada en iteradores:

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

Demo en vivo en Coliru

zett42
fuente
0

como lo hizo @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 ;
}

Entonces podemos llamar como sigue:

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

al igual que Python:

>>> " ".join( map(str, [1,2,3,4]) )
小 文件
fuente
0

Yo uso algo como esto

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
shinshillov
fuente
0

Comencé con la respuesta de @ sbi, pero la mayoría de las veces terminé conectando la cadena resultante a una secuencia, por lo que creé la siguiente solución que se puede canalizar a una secuencia sin la sobrecarga de crear la cadena completa en la memoria.

Se utiliza de la siguiente manera:

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

Donde string_join.h es:

#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);
}
Nathan Phillips
fuente
0

He escrito el siguiente código. Está basado en C # string.join. Funciona con std :: string y std :: wstring y muchos tipos de vectores. (ejemplos en comentarios)

Llámalo así:

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

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

Código:

// 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();
}
Chico al azar
fuente
0

A continuación, se muestra una forma sencilla de convertir un vector de números enteros en cadenas.

#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;
}
Amit
fuente
0

unirse usando la función de plantilla

Utilicé un template functionpara unir los vectorelementos y eliminé la ifdeclaración innecesaria iterando solo del primero al penúltimo elemento en el vectory luego uniéndome al último elemento después del forciclo. Esto también evita la necesidad de código adicional para eliminar el separador adicional al final de la cadena unida. Por lo tanto, no hay ifdeclaraciones que ralenticen la iteración ni separadores superfluos que necesiten arreglarse.

Esto produce un elegante llamada de función para unirse a una vectorde string, integero double, etc.

Escribí dos versiones: una devuelve una cadena; el otro escribe directamente en una secuencia.

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

Salida

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
tim
fuente