Je voudrais construire un std::string
de std::vector<std::string>
.
Je pourrais utiliser std::stringsteam
, mais imaginez qu'il existe un moyen plus court:
std::string string_from_vector(const std::vector<std::string> &pieces) {
std::stringstream ss;
for(std::vector<std::string>::const_iterator itr = pieces.begin();
itr != pieces.end();
++itr) {
ss << *itr;
}
return ss.str();
}
Sinon, comment pourrais-je faire cela?
std::string s;
for (std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i)
s += *i;
return s;
std::string s;
std::for_each(v.begin(), v.end(), [&](const std::string &piece){ s += piece; });
return s;
std::string s;
for (const auto &piece : v) s += piece;
return s;
N'utilisez pas std::accumulate
Pour la concaténation de chaînes , c'est un classique algorithme de Schlemiel le peintre , encore pire que l'exemple habituel utilisant strcat
en C. Sans la sémantique de déplacement C++ 11, cela entraîne deux copies inutiles de l'accumulateur pour chaque élément du vecteur. Même avec la sémantique des mouvements, cela entraîne toujours une copie inutile de l'accumulateur pour chaque élément.
Les trois exemples ci-dessus sont O (n) .
std::accumulate
Est O (n²) pour les chaînes.
Vous pouvez créer
std::accumulate
O(n) pour les chaînes en fournissant un foncteur personnalisé:std::string s = std::accumulate(v.begin(), v.end(), std::string{}, [](std::string &s, const std::string &piece) -> decltype(auto) { return s += piece; });
Notez que
s
doit être une référence à non-const, le type de retour lambda doit être une référence (d'oùdecltype(auto)
), et le corps doit utiliser+=
Et non+
.
Dans la version actuelle de ce qui devrait devenir C++ 20, la définition de std::accumulate
A été modifiée pour utiliser std::move
Lors de l'ajout à l'accumulateur, donc à partir de C++ 20 et suivants, accumulate
sera O (n) pour les chaînes, et peut être utilisé comme une ligne:
std::string s = std::accumulate(v.begin(), v.end(), std::string{});
Vous pouvez utiliser la fonction standard std::accumulate()
du <numeric>
header (cela fonctionne parce qu'une surcharge de operator +
est défini pour string
s qui retourne la concaténation de ses deux arguments):
#include <vector>
#include <string>
#include <numeric>
#include <iostream>
int main()
{
std::vector<std::string> v{"Hello, ", " Cruel ", "World!"};
std::string s;
s = accumulate(begin(v), end(v), s);
std::cout << s; // Will print "Hello, Cruel World!"
}
Alternativement, vous pouvez utiliser un cycle for
plus petit et plus efficace:
#include <vector>
#include <string>
#include <iostream>
int main()
{
std::vector<std::string> v{"Hello, ", "Cruel ", "World!"};
std::string result;
for (auto const& s : v) { result += s; }
std::cout << result; // Will print "Hello, Cruel World!"
}
Pourquoi ne pas simplement utiliser operator + pour les additionner?
std::string string_from_vector(const std::vector<std::string> &pieces) {
return std::accumulate(pieces.begin(), pieces.end(), std::string(""));
}
std :: accumulate utilise std :: plus sous le capot par défaut, et l'ajout de deux chaînes est une concaténation en C++, car l'opérateur + est surchargé pour std :: string.
Mon choix personnel serait la boucle for basée sur la plage, comme dans réponse d'Oktalist .
Boost propose également une belle solution:
#include <boost/algorithm/string/join.hpp>
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> v{"first", "second"};
std::string joined = boost::algorithm::join(v, ", ");
std::cout << joined << std::endl;
}
Cela imprime:
première seconde
Dans tous les cas, je trouve l'approche std::accumulate()
une mauvaise utilisation de cet algorithme (quels que soient les problèmes de complexité).
Un peu tard pour la fête, mais j'ai aimé le fait qu'on puisse utiliser des listes d'initialisation:
std::string join(std::initializer_list<std::string> i)
{
std::vector<std::string> v(i);
std::string res;
for (const auto &s: v) res += s;
return res;
}
Ensuite, vous pouvez simplement invoquer (style Python):
join({"Hello", "World", "1"})
Google Abseil a la fonction absl :: StrJoin qui fait ce dont vous avez besoin.
Exemple de leur fichier en-tête . Notez que le séparateur peut également être ""
// std::vector<std::string> v = {"foo", "bar", "baz"};
// std::string s = absl::StrJoin(v, "-");
// EXPECT_EQ("foo-bar-baz", s);
Si ne nécessite aucun espace de fin, utilisez accumulate
défini dans <numeric>
avec jointure lambda personnalisée.
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
int main() {
vector<string> v;
string s;
v.Push_back(string("fee"));
v.Push_back(string("fi"));
v.Push_back(string("foe"));
v.Push_back(string("fum"));
s = accumulate(begin(v), end(v), string(),
[](string lhs, const string &rhs) { return lhs.empty() ? rhs : lhs + ' ' + rhs; }
);
cout << s << endl;
return 0;
}
Production:
fee fi foe fum
Avec c ++ 11, la manière stringstream n'est pas trop effrayante:
#include <vector>
#include <string>
#include <algorithm>
#include <sstream>
#include <iostream>
int main()
{
std::vector<std::string> v{"Hello, ", " Cruel ", "World!"};
std::stringstream s;
std::for_each(begin(v), end(v), [&s](const std::string &elem) { s << elem; } );
std::cout << s.str();
}