2009-09-16 14 views
57

Mam vector<int> pojemnik, który ma całkowite (np {1,2,3,4}) i chciałbym przekonwertować na ciąg postaciC++ wektor ciąg

"1,2,3,4" 

Co jest najczystszym sposób to zrobić w C++? W Pythonie jest to w jaki sposób to zrobić:

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

Ściśle związane: http://stackoverflow.com/questions/4850473/pretty-print-c- stl-containers –

Odpowiedz

70

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

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

Można również skorzystać z std::for_each zamiast.

+0

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

+1

Tak, jak nazywamy wektor. –

+0

Dzięki! Teraz muszę przekonwertować ss na typ char * (do użytku z funkcją C). Wszelkie sugestie, jak to zrobić? – dzhelil

39

Używając std :: copy i std :: ostream_iterator możemy uzyskać coś tak eleganckiego jak pyton.

Zobacz this question dla małej klasy pisałem. To nie wydrukuje końcowego przecinka. Także, jeśli założymy, że C++ 14 będzie nadal dają nam zakres ekwiwalentów w oparciu o algorytmy tak:

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

Myślę, że to nie jest odpowiednik sprzężenia Pythona - na końcu wstawi dodatkowe ",". –

+2

Nie jest tożsame, ale równie eleganckie (w rzeczywistości myślę, że bardziej, ale to tylko opinia). –

+17

Oczywiście elegancja jest subiektywna. Więc jeśli ty i dwoje innych ludzi wolicie dłuższy, bardziej powtarzalny kod, który nie działa, to jest bardziej elegancki ;-p –

15

Inną alternatywą jest wykorzystanie std::copy a klasa 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); 
nie

także równie miły jak Python. W tym celu stworzyłem join funkcję:

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 wykorzystać go tak:

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

Można zapytać, dlaczego przeszedł w iteratorów. Właściwie chciałem odwrócić tablicę, więc użyłem go tak:

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

Idealnie chciałabym szablon się do punktu, w którym może on ustalić typ char i używać string-strumienie, ale Nie mogłem tego jeszcze rozgryźć.

+0

Ponieważ byłoby za dużo na komentarz, wysłałem odpowiedź (http://stackoverflow.com/questions/1430757/1432040#1432040), która próbuje rozwiązać zagadkę podaną w ostatnim zdaniu. – sbi

+0

Twoja funkcja 'join' może być również używana z wektorami? Proszę dać przykład, jestem nowy w C++. – Noitidart

+0

Czy potrafisz zmienić iterator na przedrostek w odpowiedzi? –

9

To jest po prostu próbą rozwiązania zagadki podane przez 1800 INFORMATION's remark na jego drugie rozwiązanie brakuje genericity, a nie próbą 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).

+0

Visual Studio 2013 jest bardzo zdezorientowany przez typedefs. Nie, że mogłeś wiedzieć, że w 2009 roku. – Grault

+0

@Jes: Patrzyłem na to przez 5 minut, ale nie mogłem zrozumieć, co VS może potknąć. Czy mógłbyś to sprecyzować? – sbi

+0

Przykro mi, myślę, że próbowałem połączyć obiekty bez << przeciążenia, które teraz sobie uświadamiam jest nieodpowiedni dla twojego kodu. Nie mogę spowodować, że twój kod nie zostanie skompilowany z wektorem ciągów. Na marginesie, społeczność VS 2013 jest darmowa i funkcjonalna, w przeciwieństwie do wersji "Express". – Grault

2

Podoba mi się odpowiedź 1800. Jednak chciałbym przenieść pierwszej iteracji z pętli, jak w wyniku 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że to oczywiście być zmniejszona do mniejszej liczby sprawozdań, 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; 
} 
+0

Nie powinieneś używać post-increment dla nieznanych typów iteratorów. To może być drogie. (Oczywiście, gdy mamy do czynienia ze strunami, może to nie zrobić dużej różnicy, ale gdy nauczysz się nawyku ...) – sbi

+0

Przyrost postów jest w porządku, o ile użyjesz zwracanej wartości temporii. np. "result.append (* it); ++ it;" jest prawie zawsze tak kosztowna jak "result.append (* it ++);" drugi ma jedną dodatkową kopię iteratora. – iain

+0

Ups, właśnie zauważyłem przyrost postu w pętli for. skopiuj i wklej błąd. Naprawiłem post. – iain

2

Istnieje kilka interesujących prób dostarczenia eleganckiego rozwiązania problemu. Miałem pomysł wykorzystania strumieni szablonowych, aby skutecznie odpowiedzieć na oryginalny dylemat PO.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ą ponownego użycia. Ponieważ C++ nie zapewnia eleganckiego sposobu łączenia ciągów w standardowej bibliotece (którą widziałem), ważne staje się stworzenie takiego, który jest elastyczny i możliwy do ponownego użycia. Oto mój strzał w nim:

// 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 korzystać z tego, można po prostu zrobić coś jak następuje:

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

Uwaga jak wykorzystanie strumieni czyni to rozwiązanie bardzo elastyczne, jak możemy przechowywać nasze wynikiem jest strumień łańcuchowy, aby odzyskać go później, lub możemy napisać bezpośrednio do standardowego wyjścia, pliku, a nawet do połączenia sieciowego zaimplementowanego jako strumień. Typ drukowania musi być po prostu powtarzalny i kompatybilny ze strumieniem źródłowym. STL zapewnia różne strumienie, które są kompatybilne z szeroką gamą typów. Więc możesz naprawdę iść z tym do miasta. Z góry mojej głowy, twój wektor może być int, float, double, string, unsigned int, SomeObject * i więcej.

8

Wiele szablonów/pomysłów. Mój nie jest tak ogólny ani skuteczny, ale ja po prostu miałem ten sam problem i chciałem go wrzucić jako coś krótkiego i słodkiego. Wygrywa na najkrótszym liczby 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 chcieć zmienić to na "auto &", aby uniknąć dodatkowych kopii i uzyskać łatwy wzrost wydajności. –

+0

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

13

można użyć std :: gromadzić. 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); 
        }); 
4

Jeśli chcesz zrobić std::cout << join(myVector, ",") << std::endl;, można 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)); 
} 

Uwaga, to rozwiązanie czyni przyłączyć bezpośrednio do strumienia wyjściowego zamiast tworzyć bufor wtórnego i będzie działać z każdym typem, który ma operatora < < na ostream.

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

10

Z i C++ Boost, 11 to można osiągnąć tak:

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

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

zgłosił Praetorian.

może obsługiwać wartości dowolnego typu takiego:

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

jak @capone zrobił,

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

Wtedy możemy zadzwonić jak następuje:

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

jak pyton:

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

używam coś jak ten

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 
1

Utworzyłem plik nagłówka pomocnika dodać rozszerzony dołączyć wsparcie.

Wystarczy dodać poniższy kod do ogólnego pliku nagłówkowego i uwzględnić go w razie potrzeby.

wykorzystania Przykłady:

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

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

Zacząłem z odpowiedzią @ SBI, ale przez większość czasu skończyło się potokiem wynikowy ciąg do strumienia tak utworzonego poniższe rozwiązanie, które można przesłać strumieniowo do strumienia bez narzutu tworzenia pełnego ciągu w pamięci.

Stosowany jest 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 jest:

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

Oto rodzajowy C++ 11 rozwiązanie, które pozwoli Ci zrobić

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

Napisałem poniższy kod. Jest oparty na C# string.join. Działa ze std :: string i std :: wstring oraz wieloma typami wektorów. (przykłady w komentarzach)

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