web-dev-qa-db-fra.com

Comment construire une chaîne std :: à partir d'une chaîne std :: vector <string>?

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?

21
WilliamKF

C++ 03

std::string s;
for (std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i)
    s += *i;
return s;

C++ 11 (le sous-ensemble MSVC 2010)

std::string s;
std::for_each(v.begin(), v.end(), [&](const std::string &piece){ s += piece; });
return s;

C++ 11

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

C++ 20

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{});
55
Oktalist

Vous pouvez utiliser la fonction standard std::accumulate() du <numeric> header (cela fonctionne parce qu'une surcharge de operator + est défini pour strings 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!"
}
35
Andy Prowl

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.

8
bstamour

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é).

6
Ali

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"})
2
Benjamin K.

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

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
1
user9494810

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