web-dev-qa-db-fra.com

Comment imploser un vecteur de chaînes dans une chaîne (de manière élégante)

Je cherche le moyen le plus élégant d'imploser un vecteur de chaînes dans une chaîne. Voici la solution que j'utilise maintenant:

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

Y en at-il d'autres?

60
ezpresso

Utilisez boost::algorithm::join(..):

#include <boost/algorithm/string/join.hpp>
...
std::string joinedString = boost::algorithm::join(elems, delim);

Voir aussi cette question .

116
Andre Holzner
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));

(inclure <string>, <vector>, <sstream> et <iterator>)

Si vous voulez avoir une extrémité propre (pas de délimiteur final), regardez ici

23
sehe

Vous devez utiliser std::ostringstream plutôt que std::string pour générer la sortie (vous pouvez ensuite appeler sa méthode str() à la fin pour obtenir une chaîne; votre interface n'a donc pas besoin de changer, seule la variable temporaire s).

À partir de là, vous pouvez utiliser std::ostream_iterator comme suit:

copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim)); 

Mais cela a deux problèmes:

  1. delim doit maintenant être un const char*, plutôt qu'un seul char. Pas grave.
  2. std::ostream_iterator écrit le délimiteur après chaque élément, y compris le dernier. Donc vous devez soit effacer le dernier à la fin, soit écrire votre propre version de l’itérateur qui n’a pas cette gêne. Ce serait la peine de faire cela si vous avez beaucoup de code qui a besoin de choses comme ça; sinon, il serait préférable d’éviter tout le bazar (utilisez ostringstream mais pas ostream_iterator).
21
John Zwinck
string join(const vector<string>& vec, const char* delim)
{
    stringstream res;
    copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
    return res.str();
}
9
BlackMamba

Parce que j'aime les one-liners (ils sont très utiles pour toutes sortes de choses étranges, comme vous le verrez à la fin), voici une solution utilisant std :: accumulate et 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; 
    } )

Je trouve cette syntaxe utile avec l'opérateur de flux, où je ne souhaite pas avoir toutes sortes de logique étrange hors de portée de l'opération de flux, juste pour faire une simple jointure de chaîne. Considérons par exemple cette instruction return de la méthode qui formate une chaîne à l'aide d'opérateurs de flux (à l'aide de 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();
9
Guss

Une version qui utilise 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";
}
3
Robᵩ

Utiliser une partie de answer pour une autre question vous donne une jointure ceci, basée sur un séparateur sans virgule de fin,

Usage:

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

Code:

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

Surtout avec les plus grandes collections, vous voulez éviter de vérifier si vous ajoutez toujours le premier élément ou non pour vous assurer qu'il n'y a pas de séparateur final ...

Donc, pour la liste vide ou à un seul élément, il n'y a pas d'itération du tout.

Les plages vides sont triviales: retour "".

Un seul élément ou plusieurs éléments peuvent être parfaitement gérés par 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;
         });
};

Exemple en cours d'exécution (require C++ 14): http://cpp.sh/8uspd

2
xtofl

Voici un autre qui n'ajoute pas le délimiteur après le dernier élément:

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 "";
2
Darien Pardinas

qu'en est-il de la solution simple et stupide?

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

Voici ce que j'utilise, simple et flexible

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

en utilisant:

string a = joinList({ "a", "bbb", "c" }, "!@#");

sortie:

a!@#bbb!@#c
1
user1438233

ajoutez simplement !! String s = "";

for (int i = 0; i < doc.size(); i++)   //doc is the vector
    s += doc[i];
1
cleanplay

Solution légèrement longue, mais n'utilise pas std::ostringstream, et ne nécessite pas de piratage pour supprimer le dernier délimiteur.

http://www.ideone.com/hW1M9

Et le code:

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

essayez ceci, mais utilisez vector au lieu de list

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()+ "]";
}
0
D. Pérez