Szukam najbardziej eleganckiego sposobu na implodowanie wektora strun w ciąg. Poniżej znajduje się rozwiązanie, którego teraz używam:
static std::string& implode(const std::vector<std::string>& elems, char delim, std::string& s)
{
for (std::vector<std::string>::const_iterator ii = elems.begin(); ii != elems.end(); ++ii)
{
s += (*ii);
if ( ii + 1 != elems.end() ) {
s += delim;
}
}
return s;
}
static std::string implode(const std::vector<std::string>& elems, char delim)
{
std::string s;
return implode(elems, delim, s);
}
Czy są tam inni?
Odpowiedzi:
Zastosowanie
boost::algorithm::join(..)
:#include <boost/algorithm/string/join.hpp> ... std::string joinedString = boost::algorithm::join(elems, delim);
Zobacz także to pytanie .
źródło
std::vector<std::string> strings; const char* const delim = ", "; std::ostringstream imploded; std::copy(strings.begin(), strings.end(), std::ostream_iterator<std::string>(imploded, delim));
(obejmuje
<string>
,<vector>
,<sstream>
i<iterator>
)Jeśli chcesz mieć czysty koniec (bez końcowego separatora), zajrzyj tutaj
źródło
std::ostream_iterator
konstruktora na końcu strumienia.Powinieneś
std::ostringstream
raczej używać niżstd::string
budować dane wyjściowe (wtedy możesz wywołać jegostr()
metodę na końcu, aby uzyskać ciąg, więc twój interfejs nie musi się zmieniać, tylko tymczasowys
).Stamtąd możesz zmienić na używanie
std::ostream_iterator
, na przykład:copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim));
Ale ma to dwa problemy:
delim
teraz musi byćconst char*
, a nie pojedynczymchar
. Nie ma sprawy.std::ostream_iterator
zapisuje separator po każdym elemencie, łącznie z ostatnim. Więc musiałbyś albo usunąć ostatni na końcu, albo napisać własną wersję iteratora, która nie ma tej irytacji. Warto zrobić to drugie, jeśli masz dużo kodu, który potrzebuje takich rzeczy; w przeciwnym razie najlepiej byłoby uniknąć całego bałaganu (tj. używać,ostringstream
ale nieostream_iterator
).źródło
Ponieważ kocham jednolinijki (są bardzo przydatne do wszelkiego rodzaju dziwnych rzeczy, jak zobaczysz na końcu), oto rozwiązanie wykorzystujące std ::umulate i C ++ 11 lambda:
std::accumulate(alist.begin(), alist.end(), std::string(), [](const std::string& a, const std::string& b) -> std::string { return a + (a.length() > 0 ? "," : "") + b; } )
Uważam, że ta składnia jest przydatna w przypadku operatora strumienia, w którym nie chcę mieć wszelkiego rodzaju dziwnej logiki poza zakresem operacji strumienia, tylko po to, aby wykonać proste łączenie ciągu. Rozważmy na przykład tę instrukcję return from method, która formatuje ciąg przy użyciu operatorów strumienia (using std;):
return (dynamic_cast<ostringstream&>(ostringstream() << "List content: " << endl << std::accumulate(alist.begin(), alist.end(), std::string(), [](const std::string& a, const std::string& b) -> std::string { return a + (a.length() > 0 ? "," : "") + b; } ) << endl << "Maybe some more stuff" << endl )).str();
Aktualizacja:
Jak zauważył @plexando w komentarzach, powyższy kod cierpi na niewłaściwe zachowanie, gdy tablica zaczyna się od pustych ciągów, ponieważ w sprawdzaniu „pierwszego uruchomienia” brakuje poprzednich uruchomień, które nie spowodowały żadnych dodatkowych znaków, a także - dziwne jest uruchamianie sprawdzania „jest uruchamiane jako pierwsze” we wszystkich przebiegach (tj. kod jest niedoptymalizowany).
Rozwiązanie obu tych problemów jest łatwe, jeśli wiemy na pewno, że lista zawiera co najmniej jeden element. OTOH, jeśli wiemy na pewno, że lista nie zawiera przynajmniej jednego elementu, to możemy skrócić przebieg jeszcze bardziej.
Myślę, że wynikowy kod nie jest tak ładny, więc dodaję go tutaj jako The Correct Solution , ale myślę, że powyższa dyskusja nadal ma merrit:
alist.empty() ? "" : /* leave early if there are no items in the list std::accumulate( /* otherwise, accumulate */ ++alist.begin(), alist.end(), /* the range 2nd to after-last */ *alist.begin(), /* and start accumulating with the first item */ [](auto& a, auto& b) { return a + "," + b; });
Uwagi:
alist[0]
dla wektorów.[](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; })
która (w GCC 10) poprawia wydajność o więcej niż x10. Dzięki @Deduplicator za sugestię. Nadal próbuję dowiedzieć się, co się tutaj dzieje.źródło
accumulate
do ciągów. Większość pozostałych odpowiedzi to O (n), aleaccumulate
jest to O (n ^ 2), ponieważ tworzy tymczasową kopię akumulatora przed dołączeniem każdego elementu. I nie, semantyka ruchu nie pomaga.T
ma przeciążonyoperator+
(tak jakstring
robi) lub jeśli zapewniasz własny funktor, wszystkie zakłady są wyłączone. Chociaż być może spieszyłem się, mówiąc, że semantyka ruchu nie pomaga, nie rozwiązują problemu w dwóch sprawdzonych implementacjach. Zobacz moje odpowiedzi na podobne pytania .implode
przykład PO ) jest słuszna. Są O (n), nawet jeśli nie wywołująreserve
łańcucha. Tylko rozwiązanie używające akumuluj to O (n ^ 2). Nie ma potrzeby stosowania kodu w stylu C.co z prostym głupim rozwiązaniem?
std::string String::join(const std::vector<std::string> &lst, const std::string &delim) { std::string ret; for(const auto &s : lst) { if(!ret.empty()) ret += delim; ret += s; } return ret; }
źródło
string join(const vector<string>& vec, const char* delim) { stringstream res; copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim)); return res.str(); }
źródło
Lubię używać tej jednowierszowej akumulacji (bez końcowego ogranicznika):
std::accumulate( std::next(elems.begin()), elems.end(), elems[0], [](std::string a, std::string b) { return a + delimiter + b; } );
źródło
Szczególnie w przypadku większych kolekcji chcesz uniknąć konieczności sprawdzania, czy nadal dodajesz pierwszy element, czy nie, aby upewnić się, że nie ma końcowego separatora ...
Tak więc w przypadku listy pustej lub jednoelementowej nie ma żadnej iteracji.
Puste zakresy są trywialne: zwraca „”.
Jednoelementowy lub wieloelementowy doskonale radzi sobie z
accumulate
:auto join = [](const auto &&range, const auto separator) { if (range.empty()) return std::string(); return std::accumulate( next(begin(range)), // there is at least 1 element, so OK. end(range), range[0], // the initial value [&separator](auto result, const auto &value) { return result + separator + value; }); };
Uruchomiony przykład ( wymaga C ++ 14 ): http://cpp.sh/8uspd
źródło
Wersja wykorzystująca
std::accumulate
:#include <numeric> #include <iostream> #include <string> struct infix { std::string sep; infix(const std::string& sep) : sep(sep) {} std::string operator()(const std::string& lhs, const std::string& rhs) { std::string rz(lhs); if(!lhs.empty() && !rhs.empty()) rz += sep; rz += rhs; return rz; } }; int main() { std::string a[] = { "Hello", "World", "is", "a", "program" }; std::string sum = std::accumulate(a, a+5, std::string(), infix(", ")); std::cout << sum << "\n"; }
źródło
Oto kolejny, który nie dodaje separatora po ostatnim elemencie:
std::string concat_strings(const std::vector<std::string> &elements, const std::string &separator) { if (!elements.empty()) { std::stringstream ss; auto it = elements.cbegin(); while (true) { ss << *it++; if (it != elements.cend()) ss << separator; else return ss.str(); } } return "";
źródło
Użycie części tej odpowiedzi na inne pytanie daje połączenie this, oparte na separatorze bez końcowego przecinka,
Stosowanie:
std::vector<std::string> input_str = std::vector<std::string>({"a", "b", "c"}); std::string result = string_join(input_str, ","); printf("%s", result.c_str()); /// a,b,c
Kod:
std::string string_join(const std::vector<std::string>& elements, const char* const separator) { switch (elements.size()) { case 0: return ""; case 1: return elements[0]; default: std::ostringstream os; std::copy(elements.begin(), elements.end() - 1, std::ostream_iterator<std::string>(os, separator)); os << *elements.rbegin(); return os.str(); } }
źródło
Oto, czego używam, proste i elastyczne
string joinList(vector<string> arr, string delimiter) { if (arr.empty()) return ""; string str; for (auto i : arr) str += i + delimiter; str = str.substr(0, str.size() - delimiter.size()); return str; }
za pomocą:
string a = joinList({ "a", "bbb", "c" }, "!@#");
wynik:
a!@#bbb!@#c
źródło
Nieco długie rozwiązanie, ale nie używa
std::ostringstream
i nie wymaga hakowania w celu usunięcia ostatniego ogranicznika.http://www.ideone.com/hW1M9
A kod:
struct appender { appender(char d, std::string& sd, int ic) : delim(d), dest(sd), count(ic) { dest.reserve(2048); } void operator()(std::string const& copy) { dest.append(copy); if (--count) dest.append(1, delim); } char delim; mutable std::string& dest; mutable int count; }; void implode(const std::vector<std::string>& elems, char delim, std::string& s) { std::for_each(elems.begin(), elems.end(), appender(delim, s, elems.size())); }
źródło
Możliwe rozwiązanie z operatorem trójskładnikowym
?:
.std::string join(const std::vector<std::string> & v, const std::string & delimiter = ", ") { std::string result; for (size_t i = 0; i < v.size(); ++i) { result += (i ? delimiter : "") + v[i]; } return result; }
join({"2", "4", "5"})
da ci2, 4, 5
.źródło
Z FMT możesz to zrobić.
#include <fmt/format.h> auto s = fmt::format("{}",fmt::join(elems,delim));
Ale nie wiem, czy join przejdzie do std :: format.
źródło
poprostu dodaj !! String s = "";
for (int i = 0; i < doc.size(); i++) //doc is the vector s += doc[i];
źródło
spróbuj tego, ale użyj wektora zamiast listy
template <class T> std::string listToString(std::list<T> l){ std::stringstream ss; for(std::list<int>::iterator it = l.begin(); it!=l.end(); ++it){ ss << *it; if(std::distance(it,l.end())>1) ss << ", "; } return "[" + ss.str()+ "]"; }
źródło