web-dev-qa-db-fra.com

Convertir un vecteur <int> en chaîne

J'ai un conteneur vector<int> qui a des entiers (par exemple {1,2,3,4}) et je voudrais convertir en une chaîne de la forme 

"1,2,3,4"

Quel est le moyen le plus propre de faire cela en C++? En Python, voici comment je le ferais:

>>> array = [1,2,3,4]
>>> ",".join(map(str,array))
'1,2,3,4'
79
dzhelil

Pas aussi élégant que Python, mais rien n’est aussi élégant que Python en C++.

Vous pouvez utiliser une 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();

Vous pouvez également utiliser std::for_each à la place.

85
Brian R. Bondy

En utilisant std :: copy et std :: ostream_iterator, nous pouvons obtenir quelque chose d'aussi élégant que python.

#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>


int main()
{
    int  array[] = {1,2,3,4};

    std::copy(array, array+4, std::ostream_iterator<int>(std::cout,","));
}

Voir cette question pour un petit cours, j’ai écrit. Cela n'imprimera pas la virgule de fin. De plus, si nous supposons que C++ 14 continuera à nous donner des équivalents d'algorithmes basés sur la distance, comme ceci:

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
}
42
Martin York

Une autre alternative est l'utilisation de std::copy et de la classe 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);

Pas aussi beau que Python . Pour cela, j'ai créé une fonction 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;
}

Puis utilisé comme ça:

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

Vous pourriez demander pourquoi je suis passé dans les itérateurs. En fait, je voulais inverser le tableau, je l'ai donc utilisé comme ceci:

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

Dans l’idéal, je voudrais créer un modèle qui permette de déduire le type de caractère et d’utiliser des chaînes de caractères, mais je n’ai pas encore compris.

16
1800 INFORMATION

Vous pouvez utiliser std :: accumulate. Considérez l'exemple suivant

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

Avec Boost et C++ 11, cela pourrait être réalisé comme suit:

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

Enfin presque. Voici l'exemple complet:

#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édit à Praetorian .

Vous pouvez gérer n'importe quel type de valeur comme ceci:

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

Ceci est juste une tentative de résoudre l'énigme donnée par la remarque de 1800 INFORMATION sur sa deuxième solution manquant de généricité, pas une tentative de réponse à la 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();
}

Fonctionne sur ma machine (TM). 

10
sbi

Beaucoup de modèles/idées. Le mien n’est ni aussi générique ni aussi efficace, mais j’ai eu juste le même problème et je voulais ajouter cela à quelque chose de court et d’adorable. Il gagne sur le plus petit nombre de lignes ... :)

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);
7
Joe Schneider

Si vous voulez faire std::cout << join(myVector, ",") << std::endl;, vous pouvez faire quelque chose comme:

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

Notez que cette solution effectue la jointure directement dans le flux de sortie plutôt que de créer un tampon secondaire et fonctionnera avec tous les types ayant l'opérateur << sur un ostream.

Cela fonctionne également lorsque boost::algorithm::join() échoue, lorsque vous avez un vector<char*> au lieu d'un vector<string>.

4
mheyman

Il existe des tentatives intéressantes pour apporter une solution élégante au problème. J'ai eu l'idée d'utiliser des flux basés sur des modèles pour répondre efficacement au dilemme initial du PO. Bien que ce soit un ancien post, j'espère que les futurs utilisateurs qui tomberont sur cela trouveront ma solution bénéfique.

Premièrement, certaines réponses (y compris la réponse acceptée) ne favorisent pas la réutilisation. Comme C++ ne fournit pas un moyen élégant de joindre des chaînes dans la bibliothèque standard (que j'ai déjà vues), il devient important de créer une chaîne flexible et réutilisable. Voici mon coup de feu à cela:

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

Maintenant, pour utiliser cela, vous pouvez simplement faire quelque chose comme ceci:

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

Notez comment l'utilisation des flux rend cette solution incroyablement flexible, car nous pouvons stocker notre résultat dans un stringstream pour la récupérer ultérieurement, ou écrire directement dans la sortie standard, dans un fichier ou même sur une connexion réseau implémentée en tant que flux. Le type en cours d'impression doit simplement être itératif et compatible avec le flux source. STL fournit divers flux compatibles avec un grand nombre de types. Donc, vous pouvez vraiment aller en ville avec ça. De mon côté, votre vecteur peut être int, float, double, chaîne, unsigned int, SomeObject *, et plus encore. 

2
David Peterson

J'aime la réponse de 1800. Cependant, je voudrais déplacer la première itération de la boucle car le résultat de l'instruction if ne change qu'une fois après la première itération.

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

Cela peut bien sûr être réduit à moins de déclarations si vous aimez:

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;
}
2
iain
string s;
for (auto i : v)
    s += (s.empty() ? "" : ",") + to_string(i);
2
chenfy27

J'ai créé un fichier d'en-tête d'assistance pour ajouter une prise en charge étendue des jointures.

Ajoutez simplement le code ci-dessous à votre fichier d’en-tête général et incluez-le si nécessaire.

Exemples d'utilisation:

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

Le code derrière la scène:

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

Voici une solution générique C++ 11 qui vous permettra de faire

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

Le code est:

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

Voici un moyen simple et pratique de convertir des éléments de vector en 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();
}

Vous devez #include <sstream> pour ostringstream.

1
mrts

J'utilise quelque chose comme ça

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

comme @capone l'a fait, 

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

Ensuite, nous pouvons appeler comme suit:

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

juste comme le python:

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

Étendre la tentative de @sbi à une solution générique qui n'est pas limitée à std::vector<int> ou à un type de chaîne de retour spécifique. Le code présenté ci-dessous peut être utilisé comme ceci:

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

Dans le code d'origine, la déduction d'argument de modèle ne permet pas de produire le type de chaîne renvoyé correct si le séparateur est un littéral de chaîne (comme dans les exemples ci-dessus). Dans ce cas, les typedefs comme Str::value_type dans le corps de la fonction sont incorrects. Le code suppose que Str est toujours un type comme std::basic_string, donc il échoue évidemment pour les littéraux de chaîne.

Pour résoudre ce problème, le code suivant tente de déduire uniquement le type caractère de l'argument séparateur et l'utilise pour générer un type de chaîne de retour par défaut. Ceci est réalisé en utilisant boost::range_value , qui extrait le type d'élément du type donné plage.

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

Maintenant, nous pouvons facilement fournir une surcharge basée sur une plage qui est simplement transmise à la surcharge basée sur un itérateur:

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

Démo en direct sur Coliru

0
zett42

J'ai commencé avec la réponse de @ sbi mais la plupart du temps, j'ai fini par rediriger la chaîne résultante vers un flux, créant ainsi la solution ci-dessous pouvant être redirigée vers un flux sans la surcharge de la création de la chaîne complète en mémoire.

Il est utilisé comme suit:

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

Où string_join.h est:

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

J'ai écrit le code suivant. Il est basé sur C # string.join. Cela fonctionne avec std :: string et std :: wstring et de nombreux types de vecteurs. (exemples dans les commentaires)

Appelez ça comme ça:

 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:
// 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();
}
0
RandomGuy