Konwertuj wektor <int> na ciąg


93

Mam vector<int>kontener zawierający liczby całkowite (np. {1,2,3,4}) i chciałbym zamienić na ciąg znaków w postaci

"1,2,3,4"

Jaki jest najczystszy sposób na zrobienie tego w C ++? W Pythonie tak bym to zrobił:

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

Odpowiedzi:


96

Zdecydowanie nie jest tak elegancki jak Python, ale nic nie jest tak eleganckie jak Python w C ++.

Możesz użyć 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();

Możesz także użyć std::for_eachzamiast tego.


Myślę, że masz na myśli array.size () nie v.size (), nie?
Mark Elliot,

1
tak, jakkolwiek nazywa się wektor.
Brian R. Bondy,

2
Tak powinno być std::string s = ss.str(). Jeśli chcesz const char*, użyj s.c_str(). (Zauważ, że chociaż poprawna składniowo, ss.str().c_str()da ci to, const char*które wskazuje na tymczasowy, który przestanie istnieć pod koniec pełnego wyrażenia. To boli.)
sbi

1
dlaczego nie po prostu użyć string.append?
Baiyan Huang

12
odpowiedź jest niekompletna bez#include <sstream>
renadeen

44

Używając std :: for_each i lambda możesz zrobić coś ciekawego.

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

Zobacz to pytanie dla małej klasy, którą napisałem. Nie spowoduje to wydrukowania końcowego przecinka. Również jeśli założymy, że C ++ 14 będzie nadal dawał nam odpowiedniki algorytmów w oparciu o zakresy, takie jak ten:

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
}

12
Myślę, że to nie jest równoważne złączeniu w Pythonie - na końcu wstawi dodatkowe ",".
1800 INFORMACJE

2
Nie równoważne, ale równie eleganckie (tak naprawdę myślę bardziej, ale to tylko opinia).
Martin York

21
Oczywiście elegancja jest subiektywna. Więc jeśli ty i dwie inne osoby wolicie dłuższy, bardziej powtarzalny kod, który nie działa, to jest bardziej elegancki ;-p
Steve Jessop

1
Możesz zignorować ostatni przecinek, używając funkcji składowej string :: substr. Przypisz podciąg od 0 do n-1 do zmiennej wynikowej.
Dan

@SteveJessop: Lepiej?
Martin York

24

Możesz użyć std ::umulate. Rozważmy następujący przykład

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

','powinno być","
Matt

2
@Matt stringKlasa ma przeciążenie dla +operatora, który może również akceptować znaki. Więc ','jest w porządku.
Pavan Manjunath

19

Inną alternatywą jest użycie std::copyi ostream_iteratorklasa:

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

Również nie tak przyjemny jak Python. W tym celu stworzyłem joinfunkcję:

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

Następnie użyłem tego w ten sposób:

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

Możesz zapytać, dlaczego zdałem w iteratorach. Cóż, właściwie chciałem odwrócić tablicę, więc użyłem tego w ten sposób:

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

Idealnie chciałbym opracować szablon do punktu, w którym można wywnioskować typ znaku i użyć strumieni ciągów, ale nie mogłem jeszcze tego rozgryźć.


Ponieważ byłoby to zbyt wiele na komentarz, zamieściłem odpowiedź ( stackoverflow.com/questions/1430757/1432040#1432040 ), która próbuje rozwiązać zagadkę podaną w Twoim ostatnim zdaniu.
sbi

Twoja joinfunkcja może być również używana z wektorami? Proszę podać przykład, jestem nowy w C ++.
Noitidart

Czy możesz zmienić iterator na preinkrement w odpowiedzi?
Millie Smith

14

Z Boost i C ++ 11 można to osiągnąć w następujący sposób:

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

Cóż prawie. Oto pełny przykład:

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

Podziękowania dla Praetorian .

Możesz obsłużyć dowolny typ wartości, taki jak ten:

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

11

To tylko próba rozwiązania zagadki podanej w uwadze 1800 INFORMATION, dotyczącej jego drugiego rozwiązania pozbawionego ogólności, a nie próba odpowiedzi na pytanie:

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

Działa na moim komputerze (TM).


Visual Studio 2013 jest bardzo zdezorientowany przez typy czcionek. Nie żebyś mógł to wiedzieć w 2009 roku.
Grault

@Jes: Patrzę na to teraz przez 5 minut, ale nie mogłem dowiedzieć się, o co VS może się potknąć. Czy mógłbyś to sprecyzować?
sbi

Przepraszam, myślę, że próbowałem łączyć obiekty bez << przeciążeń, co, jak teraz, zdaję sobie sprawę, jest nieodpowiednie dla twojego kodu. Nie mogę spowodować, aby twój kod nie kompilował się z wektorem ciągów. Na marginesie, Społeczność VS 2013 jest bezpłatna i zawiera wiele funkcji, w przeciwieństwie do wersji „Express”.
Grault

@Jes: Powinno to działać z każdym typem, który może być przesyłany strumieniowo (tj. Jest operator<<przeciążony). Oczywiście typ bez operator<<może powodować bardzo mylące komunikaty o błędach.
sbi

Niestety, to nie skompilować: join(v.begin(), v.end(), ","). Wyprowadzenie argumentu szablonu nie daje prawidłowego wyniku, jeśli separgument jest literałem ciągu. Moja próba rozwiązania tego problemu . Zapewnia również bardziej nowoczesne przeciążenie oparte na zakresie.
zett42

7

Wiele szablonów / pomysłów. Mój nie jest tak ogólny ani wydajny, ale miałem ten sam problem i chciałem wrzucić to do miksu jako coś krótkiego i słodkiego. Wygrywa na najmniejszej liczbie linii ... :)

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

1
Dzięki za uproszczony kod. Może warto zmienić to na „auto &”, aby uniknąć dodatkowych kopii i uzyskać łatwy wzrost wydajności.
Millie Smith

Zamiast używać substr(...), użyj, pop_back()aby usunąć ostatni znak, staje się wtedy znacznie bardziej przejrzysty i czysty.
ifyalciner

4

Jeśli chcesz std::cout << join(myVector, ",") << std::endl;, możesz zrobić coś takiego:

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

Zauważ, że to rozwiązanie wykonuje łączenie bezpośrednio do strumienia wyjściowego, zamiast tworzyć dodatkowy bufor i będzie działać z każdym typem, który ma operator << na ostream.

Działa to również w przypadku boost::algorithm::join()niepowodzenia, gdy masz vector<char*>zamiast pliku vector<string>.


4
string s;
for (auto i : v)
    s += (s.empty() ? "" : ",") + to_string(i);

8
Witamy w Stack Overflow! Chociaż ten kod może rozwiązać problem, najlepiej jest dodać rozwinięcie i wyjaśnić, jak to działa dla osób, które mogą nie rozumieć tego fragmentu kodu.
paper1111

1
Obecna najczęstsza odpowiedź nie jest dużo bardziej złożona i jest to najmniejsza / najczystsza odpowiedź robocza. Nie jest tak wydajny jak w std::stringstreamprzypadku dużych tablic, ponieważ stringstreambędzie w stanie optymistycznie alokować pamięć, co prowadzi do wydajności O (n.log (n)) zamiast O (n²) dla tablicy o rozmiarze ndla tej odpowiedzi. stringstreamMoże również nie tworzyć tymczasowych ciągów dla to_string(i).
aberaud

2

Podoba mi się odpowiedź z 1800 roku. Jednak przesunąłbym pierwszą iterację poza pętlę, ponieważ instrukcja if zmienia się tylko raz po pierwszej iteracji

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

Można to oczywiście zredukować do mniejszej liczby stwierdzeń, jeśli chcesz:

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

Nie powinieneś używać post-inkrementacji dla nieznanych typów iteratorów. To może być drogie. (Oczywiście, gdy mamy do czynienia ze sznurkami, może to nie mieć większego znaczenia. Ale kiedy już nauczysz się tego nawyku ...)
sbi

Post-inkrementacja jest w porządku, o ile używasz zwracanej wartości tymczasowej. np. „result.append (* it); ++ it;” jest prawie zawsze tak kosztowny, jak "result.append (* it ++);" druga ma jedną dodatkową kopię iteratora.
innymi

Ups, właśnie zauważyłem przyrost postu w pętli for. błąd kopiowania i wklejania. Naprawiłem słupek.
innymi

1
@Ian: Kiedy uczyłem C ++, wmawiałem moim uczniom, by używali go ++iz wyjątkiem sytuacji, gdy naprawdę tego potrzebowali, i++ponieważ był to jedyny sposób, w jaki nie zapomną o tym, kiedy to zrobi różnicę. (Swoją drogą, tak samo było ze mną). Uczyli się wcześniej Javy, gdzie wszystkie rodzaje C-izmów są modne i zajęło im to kilka miesięcy (1 wykład + praca laboratoryjna tygodniowo), ale ostatecznie większość nauczyli się nawyku używania preinkrementacji.
sbi

1
@sbi: zgodził się, że zawsze domyślnie włączam też preinkrementację, nieuczciwy postincrement powstał z kopiowania czyjegoś dla pętli i zmiany tego. W mojej pierwszej odpowiedzi pomyślałem, że martwisz się o „result.append (* it ++)”, a nie o pętlę for. Byłem trochę zawstydzony, widząc przyrost postu w pętli. Niektórzy ludzie wydają się postępować zgodnie z radą, aby nie używać zbyt daleko inkrementacji postów i nigdy jej nie używać ani nie zmieniać, nawet jeśli jest to właściwe. Jednak wiem, że teraz nie należysz do tej kategorii.
innymi

2

Istnieje kilka interesujących prób eleganckiego rozwiązania problemu. Wpadłem na pomysł, aby użyć strumieni opartych na szablonach, aby skutecznie odpowiedzieć na pierwotny dylemat OP. Chociaż jest to stary post, mam nadzieję, że przyszli użytkownicy, którzy się na to natkną, uznają moje rozwiązanie za korzystne.

Po pierwsze, niektóre odpowiedzi (w tym zaakceptowana odpowiedź) nie promują możliwości ponownego użycia. Ponieważ C ++ nie zapewnia eleganckiego sposobu łączenia ciągów w bibliotece standardowej (co widziałem), ważne staje się utworzenie takiej, która jest elastyczna i nadaje się do wielokrotnego użytku. Oto moja szansa:

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

Teraz, aby z tego skorzystać, możesz po prostu zrobić coś takiego:

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

Zwróć uwagę, jak użycie strumieni sprawia, że ​​to rozwiązanie jest niezwykle elastyczne, ponieważ możemy przechowywać nasz wynik w strumieniu ciągów, aby go później odzyskać, lub możemy pisać bezpośrednio do standardowego wyjścia, pliku lub nawet do połączenia sieciowego zaimplementowanego jako strumień. Drukowany typ musi być po prostu powtarzalny i zgodny ze strumieniem źródłowym. STL zapewnia różne strumienie, które są zgodne z szeroką gamą typów. Więc naprawdę możesz pojechać z tym do miasta. U mnie, twój wektor może mieć postać int, float, double, string, unsigned int, SomeObject * i więcej.


1

Utworzyłem plik nagłówkowy pomocnika, aby dodać rozszerzoną obsługę złączeń.

Po prostu dodaj poniższy kod do ogólnego pliku nagłówkowego i dołącz go w razie potrzeby.

Przykłady użycia:

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

Kod za sceną:

#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

Oto ogólne rozwiązanie C ++ 11, które Ci na to pozwoli

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

Kod to:

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

Poniżej przedstawiono prosty i praktyczny sposób konwersji elementów z a vectorna a string:

std::string join(const std::vector<int>& numbers, const std::string& delimiter = ",") {
    std::ostringstream result;
    for (const auto number : numbers) {
        if (result.tellp() > 0) { // not first round
            result << delimiter;
        }
        result << number;
    }
    return result.str();
}

Trzeba #include <sstream>za ostringstream.


1

Rozszerzanie przy próbie @sbi w ogólnym rozwiązaniu, które nie jest ograniczone do std::vector<int>lub określonego typu zwracanego ciągu. Poniższy kod można wykorzystać w następujący sposób:

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

W oryginalnym kodzie dedukcja argumentów szablonu nie działa w celu uzyskania prawidłowego zwracanego typu ciągu, jeśli separator jest literałem ciągu (jak w przykładach powyżej). W tym przypadku definicje typu, takie jak Str::value_typew treści funkcji, są nieprawidłowe. W kodzie założono, że Strjest to zawsze typ podobny do typu std::basic_string, więc w przypadku literałów ciągów jest to oczywiste.

Aby to naprawić, poniższy kod próbuje wywnioskować tylko typ znaku z argumentu separatora i używa go do utworzenia domyślnego zwracanego typu ciągu. Osiąga się to za pomocą boost::range_value, który wyodrębnia typ elementu z danego typu zakresu .

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

Teraz możemy łatwo zapewnić przeciążenie oparte na zakresie, które po prostu przekazuje do przeciążenia opartego na iteratorze:

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 na żywo w Coliru


0

tak jak @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 ;
}

Następnie możemy zadzwonić w następujący sposób:

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

tak jak python:

>>> " ".join( map(str, [1,2,3,4]) )

0

Używam czegoś takiego

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

Zacząłem od odpowiedzi @ sbi, ale większość czasu kończyło się na przesłaniu otrzymanego ciągu do strumienia, więc stworzyłem poniższe rozwiązanie, które można przesłać do strumienia bez narzutu związanego z tworzeniem pełnego ciągu w pamięci.

Jest używany w następujący sposób:

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

Gdzie string_join.h to:

#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

Napisałem następujący kod. Jest oparty na C # string.join. Działa z std :: string i std :: wstring oraz wieloma typami wektorów. (przykłady w komentarzach)

Nazwij to tak:

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

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

Kod:

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

Oto prosty sposób na konwersję wektora liczb całkowitych na łańcuchy.

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

0

dołącz za pomocą funkcji szablonu

Użyłem a, template functionaby połączyć vectorelementy i usunąłem niepotrzebne ifstwierdzenie, przechodząc przez iterację tylko od pierwszego do przedostatniego elementu w vector, a następnie dołączając do ostatniego elementu po forpętli. Eliminuje to również potrzebę stosowania dodatkowego kodu w celu usunięcia dodatkowego separatora na końcu połączonego ciągu. Nie ma więc ifinstrukcji spowalniających iterację i zbędnego separatora, który wymaga uporządkowania.

To daje elegancki wywołanie funkcji przyłączyć się vectorSIĘ GO string, integerlubdouble itp

Napisałem dwie wersje: jedna zwraca ciąg; druga pisze bezpośrednio do strumienia.

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

Wynik

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.