Konvertieren Sie einen Vektor <int> in eine Zeichenfolge

92

Ich habe einen vector<int>Container mit ganzen Zahlen (z. B. {1,2,3,4}) und möchte in eine Zeichenfolge des Formulars konvertieren

"1,2,3,4"

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

>>> array = [1,2,3,4]
>>> ",".join(map(str,array))
'1,2,3,4'
dzhelil
quelle
1
Eng verwandt: stackoverflow.com/questions/4850473/…
Ciro Santilli 法轮功 冠状 病 六四 事件 8

Antworten:

95

Auf jeden Fall nicht so elegant wie Python, aber nichts ist so elegant wie Python in C ++.

Sie könnten eine 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();

Sie können std::for_eachstattdessen auch verwenden.

Brian R. Bondy
quelle
Ich denke du meinst array.size () nicht v.size (), nein?
Mark Elliot
1
ya wie auch immer der Vektor heißt.
Brian R. Bondy
2
Das sollte sein std::string s = ss.str(). Wenn Sie eine möchten const char*, verwenden Sie s.c_str(). (Beachten Sie, dass Sie, obwohl syntaktisch korrekt, ss.str().c_str()einen const char*Hinweis erhalten, der auf ein temporäres
Element
1
Warum nicht einfach string.append verwenden?
Baiyan Huang
12
Antwort ist unvollständig ohne#include <sstream>
renadeen
43

Mit std :: for_each und lambda können Sie etwas Interessantes tun.

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

Siehe diese Frage für eine kleine Klasse, die ich geschrieben habe. Dadurch wird das nachfolgende Komma nicht gedruckt. Auch wenn wir davon ausgehen, dass C ++ 14 uns weiterhin bereichsbasierte Äquivalente von Algorithmen wie diesen 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
}
Martin York
quelle
12
Ich denke, dies ist nicht ganz gleichbedeutend mit Pythons Join - es wird am Ende ein zusätzliches "," eingefügt.
1800 INFORMATION
2
Nicht gleichwertig, aber genauso elegant (tatsächlich denke ich eher, aber das ist nur eine Meinung).
Martin York
20
Offensichtlich ist Eleganz subjektiv. Wenn Sie und zwei andere Personen längeren, sich wiederholenden Code bevorzugen, der nicht funktioniert, ist er eleganter ;-p
Steve Jessop
1
Sie können das letzte Komma mit der Memberfunktion string :: substr ignorieren. Weisen Sie Ihrer Ergebnisvariablen den Teilstring von 0 bis n-1 zu.
Dan
@SteveJessop: Besser?
Martin York
24

Sie können std :: accumulate 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);
                     });
Kapaun
quelle
','sollte sein","
Matt
1
@Matt Die stringKlasse hat eine Überladung für +Operatoren, die auch Zeichen akzeptieren können. Also ','ist alles in Ordnung.
Pavan Manjunath
19

Eine andere Alternative ist die Verwendung von std::copyund die ostream_iteratorKlasse:

#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 joinFunktion 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 benutzte es so:

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

Sie könnten fragen, warum ich die Iteratoren bestanden habe. Nun, eigentlich wollte ich das Array umkehren, also habe ich es so verwendet:

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

Im Idealfall möchte ich eine Vorlage erstellen, die auf den Char-Typ schließen lässt, und String-Streams verwenden, aber das konnte ich noch nicht herausfinden.

1800 INFORMATIONEN
quelle
Da es für einen Kommentar zu viel wäre, habe ich eine Antwort ( stackoverflow.com/questions/1430757/1432040#1432040 ) veröffentlicht, die versucht, das in Ihrem letzten Satz gegebene Rätsel zu lösen.
sbi
Ihre joinFunktion kann auch mit Vektoren verwendet werden? Bitte geben Sie ein Beispiel, ich bin neu in C ++.
Noitidart
Können Sie den Iterator in der Antwort auf eine Vorinkrementierung ändern?
Millie Smith
14

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 Prätorianer .

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);
};
Arekolek
quelle
11

Dies ist nur ein Versuch, das Rätsel zu lösen, das sich aus der Bemerkung von 1800 INFORMATION zu seiner zweiten Lösung ohne Generizität ergibt, und kein 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 meinem Computer (TM).

sbi
quelle
Visual Studio 2013 wird durch die typedefs sehr verwirrt. Nicht, dass du das 2009
gewusst hättest
@Jes: Ich habe jetzt 5 Minuten lang darauf gestarrt, konnte aber nicht herausfinden, worüber VS stolpern könnte. Kannst du genauer sein?
sbi
Es tut mir leid, ich glaube, ich habe versucht, Objekte ohne << Überladungen zu verbinden, was mir jetzt klar ist, dass es für Ihren Code ungeeignet ist. Ich kann nicht bewirken, dass Ihr Code nicht mit einem String-Vektor kompiliert wird. Nebenbei bemerkt, VS 2013 Community ist im Gegensatz zu "Express" -Versionen sowohl kostenlos als auch funktionsreich.
Grault
@Jes: Dies sollte mit jedem Typ funktionieren, der gestreamt werden kann (dh operator<<überladen wurde). Natürlich kann ein Typ ohne operator<<sehr verwirrende Fehlermeldungen verursachen.
sbi
Dies wird leider nicht kompiliert : join(v.begin(), v.end(), ","). Der Abzug von Vorlagenargumenten führt nicht zum richtigen Ergebnis, wenn das sepArgument ein Zeichenfolgenliteral ist. Mein Versuch, eine Lösung für dieses Problem zu finden . Bietet auch eine modernere bereichsbasierte Überlastung.
Zett42
7

Viele Vorlagen / Ideen. Meins ist nicht so generisch oder effizient, aber ich hatte einfach das gleiche Problem und wollte es als etwas Kurzes und Süßes in die Mischung werfen. Es gewinnt auf der kürzesten 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);
Joe Schneider
quelle
1
Danke für den simplen Code. Vielleicht möchten Sie es in "auto &" ändern, um die zusätzlichen Kopien zu vermeiden und einige einfache Leistungssteigerungen zu erzielen.
Millie Smith
Anstatt zu verwenden substr(...), pop_back()entfernen Sie das letzte Zeichen, wird dann viel klarer und sauberer.
ifyalciner
4

Wenn Sie möchten std::cout << join(myVector, ",") << std::endl;, 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 den Join direkt in den Ausgabestream ausführt, anstatt einen sekundären Puffer zu erstellen, und mit allen Typen funktioniert, die einen Operator << für einen Ostream haben.

Dies funktioniert auch dort boost::algorithm::join(), wo ein Fehler auftritt, wenn Sie ein vector<char*>statt eines haben vector<string>.

Mheyman
quelle
4
string s;
for (auto i : v)
    s += (s.empty() ? "" : ",") + to_string(i);
chenfy27
quelle
7
Willkommen bei Stack Overflow! Während dieser Code das Problem lösen könnte, ist es am besten, eine Ausarbeitung hinzuzufügen und zu erklären, wie es für Leute funktioniert, die diesen Code möglicherweise nicht verstehen.
paper1111
1
Die aktuelle Top-Antwort ist nicht viel ausführlicher, und dies ist die kleinste / sauberste funktionierende Antwort. Nicht so effizient wie std::stringstreambei großen Arrays, da stringstreamder Speicher optimistisch zugewiesen werden kann, was zu einer Leistung von O (n.log (n)) anstelle von O (n²) für ein Array mit einer Größe nfür diese Antwort führt. Auch stringstreamkönnte nicht nur vorübergehend Strings bauen to_string(i).
Aberaud
2

Ich mag die Antwort von 1800. Ich würde jedoch die erste Iteration aus der Schleife verschieben, da sich die if-Anweisung nach der ersten Iteration nur einmal ä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 Aussagen 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;
}
iain
quelle
Sie sollten Post-Inkrement nicht für unbekannte Iteratortypen verwenden. Das könnte teuer sein. (Natürlich macht das beim Umgang mit Saiten keinen großen Unterschied. Aber sobald Sie die Gewohnheit gelernt haben ...)
sbi
Post-Inkrement ist in Ordnung, solange Sie den zurückgegebenen temporären Wert verwenden. zB "result.append (* it); ++ it;" ist fast immer so teuer wie "result.append (* it ++);" Die zweite enthält eine zusätzliche Kopie des Iterators.
iain
Hoppla, ich habe gerade das Post-Inkrement in der for-Schleife entdeckt. Fehler beim Kopieren und Einfügen. Ich habe den Beitrag repariert.
iain
1
@Ian: Als ich C ++ unterrichtete, hämmerte ich meine Schüler dazu, es zu verwenden, ++iaußer wo sie es wirklich brauchten, i++denn nur so konnten sie dies nicht vergessen, wenn es einen Unterschied machte. (Bei mir war es übrigens dasselbe.) Sie hatten zuvor Java gelernt, wo alle Arten von C-Ismen im Trend liegen, und es dauerte einige Monate (1 Vorlesung + Laborarbeit pro Woche), aber am Ende die meisten Sie lernten die Gewohnheit, Pre-Inkrement zu verwenden.
sbi
1
@sbi: stimmte zu, dass ich immer auch standardmäßig vorinkrementiere. Das betrügerische Nachinkrementieren kam vom Kopieren einer anderen Person für die Schleife und deren Änderung. In meiner ersten Antwort dachte ich, Sie wären besorgt über "result.append (* it ++)" und nicht über die for-Schleife. Es war mir ein wenig peinlich, das Post-Inkrement in der Schleife zu sehen. Einige Leute scheinen dem Rat zu folgen, das Post-Inkrement nicht zu weit zu verwenden und es niemals zu verwenden oder zu ändern, selbst wenn es angemessen ist. Allerdings weiß ich jetzt, dass Sie nicht in diese Kategorie fallen.
iain
2

Es gibt einige interessante Versuche, eine elegante Lösung für das Problem bereitzustellen. Ich hatte die Idee, Vorlagen-Streams zu verwenden, um das ursprüngliche Dilemma des OP effektiv zu beantworten. Obwohl dies ein alter Beitrag ist, hoffe ich, dass zukünftige Benutzer, die darauf stoßen, meine Lösung als vorteilhaft empfinden.

Erstens fördern einige Antworten (einschließlich der akzeptierten Antwort) nicht die Wiederverwendbarkeit. Da C ++ keine elegante Möglichkeit bietet, Zeichenfolgen in der Standardbibliothek (wie ich gesehen habe) zu verknüpfen, 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 verwenden, 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ückzugewinnen, oder direkt in den Standardausgang, eine Datei oder sogar in eine als Stream implementierte Netzwerkverbindung schreiben können. Der zu druckende Typ muss einfach iterierbar und mit dem Quelldatenstrom kompatibel sein. STL bietet verschiedene Streams, die mit einer Vielzahl von Typen kompatibel sind. Damit könnte man wirklich in die Stadt gehen. Auf der Oberseite meines Kopfes kann Ihr Vektor aus int, float, double, string, unsigned int, SomeObject * und mehr bestehen.

David Peterson
quelle
1

Ich habe eine Hilfskopfdatei erstellt, um eine erweiterte Join-Unterstützung hinzuzufügen.

Fügen Sie einfach den folgenden Code zu Ihrer allgemeinen Header-Datei hinzu und fügen Sie ihn bei Bedarf hinzu.

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 den Kulissen:

#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
quelle
1

Hier ist eine generische C ++ 11-Lösung, mit der Sie dies 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);}
n.caillou
quelle
1

Das Folgende ist eine einfache und praktische Möglichkeit, Elemente in a vectorin a umzuwandeln 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();
}

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

mrts
quelle
1

Erweiterung des Versuchs von @sbi auf eine generische Lösung , die nicht auf std::vector<int>einen bestimmten Rückgabezeichenfolgentyp 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 funktioniert die Ableitung von Vorlagenargumenten nicht, um den richtigen Rückgabezeichenfolgentyp zu erzeugen, wenn das Trennzeichen ein Zeichenfolgenliteral ist (wie in den obigen Beispielen). In diesem Fall sind die typedefs wie Str::value_typeim Funktionskörper falsch. Der Code geht davon aus, dass Stres sich immer um einen Typ handeltstd::basic_string , sodass er für Zeichenfolgenliterale offensichtlich fehlschlägt.

Um dies zu beheben, versucht der folgende Code, nur den Zeichentyp aus dem Trennzeichenargument abzuleiten, und verwendet diesen, um einen Standardrückgabezeichenfolgentyp zu erstellen. Dies wird erreicht, indem boost::range_valueder Elementtyp aus dem angegebenen Bereichstyp extrahiert wird .

#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 problemlos eine bereichsbasierte Überladung bereitstellen, die einfach an die iteratorbasierte Überladung weitergeleitet wird:

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

zett42
quelle
0

wie @capone tat,

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]) )
小 文件
quelle
0

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
Shinshillov
quelle
0

Ich begann mit der Antwort von @ sbi, aber die meiste Zeit leitete ich die resultierende Zeichenfolge an einen Stream weiter, sodass die folgende Lösung erstellt wurde, die an einen Stream weitergeleitet werden kann, ohne dass der Aufwand für die Erstellung der vollständigen Zeichenfolge im Speicher anfällt.

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 string_join.h ist:

#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
quelle
0

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)

Nennen Sie 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();
}
Zufälliger Typ
quelle
0

Hier ist eine einfache Möglichkeit, einen Vektor von Ganzzahlen in Zeichenfolgen umzuwandeln.

#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
quelle
0

Join mit Template-Funktion

Ich habe a verwendet template function, um die vectorElemente zu verbinden, und die unnötige ifAnweisung entfernt, indem ich nur die ersten bis vorletzten Elemente in der durchlaufen vectorund dann das letzte Element nach der forSchleife verbunden habe. Dadurch ist auch kein zusätzlicher Code erforderlich, um das zusätzliche Trennzeichen am Ende der verknüpften Zeichenfolge zu entfernen. Also keine ifAnweisungen, die die Iteration verlangsamen, und kein überflüssiges Trennzeichen, das aufgeräumt werden muss.

Dies erzeugt einen eleganten Funktionsaufruf eines beizutreten vectorvon string, integeroder doubleusw.

Ich habe zwei Versionen geschrieben: eine gibt eine Zeichenfolge zurück; der andere schreibt direkt in einen 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;
}

Ausgabe

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
quelle