web-dev-qa-db-de.com

Konvertieren Sie einen Vektor <int> in einen String

Ich habe einen vector<int>-Container mit Ganzzahlen (z. B. {1,2,3,4}), und ich möchte in einen String des Formulars konvertieren 

"1,2,3,4"

Was ist der sauberste Weg, dies in C++ zu tun? In Python würde ich es so machen:

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

Definitiv nicht so elegant wie Python, aber nichts ist so elegant wie Python in C++.

Sie könnten eine stringstream verwenden ...

std::stringstream ss;
for(size_t i = 0; i < v.size(); ++i)
{
  if(i != 0)
    ss << ",";
  ss << v[i];
}
std::string s = ss.str();

Sie können stattdessen auch std::for_each verwenden.

85
Brian R. Bondy

Mit std :: copy und std :: ostream_iterator können wir etwas so Elegantes wie Python bekommen.

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

Siehe diese Frage für eine kleine Klasse, die ich geschrieben habe. Das nachfolgende Komma wird nicht gedruckt. Auch wenn wir davon ausgehen, dass C++ 14 uns weiterhin bereichsabhängige Äquivalente von Algorithmen wie folgt liefert:

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

Eine andere Alternative ist die Verwendung von std::copy und der ostream_iterator-Klasse:

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

Auch nicht so schön wie Python . Zu diesem Zweck habe ich eine join-Funktion erstellt:

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

Dann benutzt es so:

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

Sie fragen sich vielleicht, warum ich die Iteratoren bestanden habe. Nun, eigentlich wollte ich das Array umkehren, also habe ich es so benutzt:

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

Im Idealfall würde ich gerne so vorgehen, dass der Char-Typ abgeleitet und String-Streams verwendet werden können, aber ich konnte das noch nicht verstehen.

16

Sie können std :: collectulate verwenden. Betrachten Sie das folgende Beispiel

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

Mit Boost und C++ 11 könnte dies folgendermaßen erreicht werden:

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

Naja fast. Hier ist das vollständige Beispiel:

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

Gutschrift an Praetorian .

Sie können jeden Werttyp wie folgt behandeln:

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

Dies ist nur ein Versuch, das Rätsel zu lösen, das durch die Bemerkung von 1800 INFORMATION zu seiner zweiten Lösung ohne Generizität gegeben wurde, und nicht der Versuch, die Frage zu beantworten: 

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

Funktioniert auf meiner Maschine (TM). 

10
sbi

Viele Vorlagen/Ideen. Mine ist nicht so generisch oder effizient, aber ich hatte einfach das gleiche Problem und wollte dieses als etwas Kurzes und Süßes in den Mix geben. Es gewinnt auf kürzester Anzahl von Zeilen ... :)

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

Wenn Sie std::cout << join(myVector, ",") << std::endl; ausführen möchten, können Sie Folgendes tun:

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

Beachten Sie, dass diese Lösung die Verknüpfung direkt mit dem Ausgabestrom durchführt, anstatt einen sekundären Puffer zu erstellen, und funktioniert mit allen Typen, die einen Operator << auf einem Ostream haben.

Dies funktioniert auch, wenn boost::algorithm::join() fehlschlägt, wenn Sie einen vector<char*> anstelle eines vector<string> haben.

4
mheyman

Es gibt einige interessante Versuche, das Problem auf elegante Weise zu lösen. Ich hatte die Idee, gestreamte Streams zu verwenden, um das ursprüngliche Dilemma der OP effektiv zu beantworten. Obwohl dies ein alter Beitrag ist, hoffe ich, dass zukünftige Benutzer, die darüber stolpern, meine Lösung für nützlich halten werden.

Erstens fördern einige Antworten (einschließlich der akzeptierten Antwort) die Wiederverwendbarkeit nicht. Da C++ keine elegante Möglichkeit bietet, Zeichenfolgen in der Standardbibliothek zu verbinden (was ich gesehen habe), ist es wichtig, eine zu erstellen, die flexibel und wiederverwendbar ist. Hier ist mein Schuss darauf:

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

Um dies zu nutzen, können Sie einfach Folgendes tun:

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

Beachten Sie, wie die Verwendung von Streams diese Lösung unglaublich flexibel macht, da wir unser Ergebnis in einem Stringstream speichern können, um es später zurückzufordern, oder wir können direkt in den Standardausgang, eine Datei oder sogar eine als Stream implementierte Netzwerkverbindung schreiben. Der zu druckende Typ muss einfach iterierbar und mit dem Quellstrom kompatibel sein. STL bietet verschiedene Streams, die mit einer Vielzahl von Typen kompatibel sind. Man könnte also wirklich in die Stadt gehen. Ganz oben in meinem Kopf kann Ihr Vektor aus int, float, double, string, unsigned int, someObject * und mehr bestehen. 

2
David Peterson

Ich mag die Antwort von 1800. Ich würde jedoch die erste Iteration aus der Schleife verschieben, da sich das Ergebnis der if -Anweisung nur einmal nach der ersten Iteration ändert

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

Dies kann natürlich auf weniger Anweisungen reduziert werden, wenn Sie möchten:

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

Ich habe eine Helper-Header-Datei erstellt, um eine erweiterte Join-Unterstützung hinzuzufügen.

Fügen Sie einfach den unten stehenden Code in Ihre allgemeine Header-Datei ein und fügen Sie ihn bei Bedarf ein.

Anwendungsbeispiele:

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

Der Code hinter der Szene:

#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

Hier ist eine generische C++ 11-Lösung, mit der Sie das tun können

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

Der Code lautet:

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

Im Folgenden finden Sie eine einfache und praktische Möglichkeit, Elemente in einer vector in eine string zu konvertieren:

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

Sie müssen #include <sstream> für ostringstream.

1
mrts

Ich benutze so etwas

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

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

Dann können wir wie folgt anrufen:

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

genau wie Python:

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

Erweiterung auf den Versuch von @sbi bei einer generischen Lösung , die nicht auf std::vector<int> oder einen bestimmten Rückgabetyp beschränkt ist. Der unten dargestellte Code kann folgendermaßen verwendet werden:

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

Im ursprünglichen Code kann der Vorlagenargument-Abzug nicht den richtigen Rückgabetypen erzeugen, wenn das Trennzeichen ein Zeichenfolgenliteral ist (wie in den obigen Beispielen). In diesem Fall sind die Typedefs wie Str::value_type im Funktionshauptteil falsch. Der Code geht davon aus, dass Str immer ein Typ wie std::basic_string ist. Daher schlägt er offensichtlich bei String-Literalen fehl.

Um dies zu beheben, versucht der folgende Code, nur den Typ Zeichen von dem Separator-Argument abzuleiten, und verwendet diesen, um einen Standardwert für den Rückgabestring zu erzeugen. Dies wird mit boost::range_value erreicht, das den Elementtyp aus dem angegebenen Bereich-Typ extrahiert.

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

Jetzt können wir leicht eine bereichsbasierte Überladung bereitstellen, die einfach auf die Iterator-basierte Überlast weiterleitet:

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 bei Coliru

0
zett42

Ich habe mit der Antwort von @ sbi angefangen, aber die meiste Zeit wurde der resultierende String an einen Stream weitergeleitet, so dass die untenstehende Lösung erstellt wurde, die an einen Stream weitergeleitet werden kann, ohne dass der gesamte String im Speicher erstellt werden muss.

Es wird wie folgt verwendet:

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

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

Ich habe den folgenden Code geschrieben. Es basiert auf C # string.join. Es funktioniert mit std :: string und std :: wstring und vielen Arten von Vektoren. (Beispiele in Kommentaren)

Nenne es so:

 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