web-dev-qa-db-fra.com

Imprimer des listes avec des virgules C++

Je sais comment faire cela dans d'autres langages, mais pas le C++, que je suis obligé d'utiliser ici.

J'imprime un ensemble de chaînes dans une liste et ils ont besoin d'une virgule entre chaque élément, mais pas d'un dernier. En Java, par exemple, j'utiliserais un constructeur de chaînes et supprimerais simplement la virgule après avoir construit ma chaîne. Comment puis-je le faire en C++?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{

    out << *iter << ", ";
}
out << endl;

J'ai d'abord essayé d'insérer ce bloc pour le faire (déplacer la virgule ici)

if (iter++ != keywords.end())
    out << ", ";
iter--;

Je déteste quand les petites choses me font trébucher.

EDIT: Merci à tous. C'est pourquoi je poste des choses comme ça ici. Tant de bonnes réponses et abordées de différentes manières. Après un semestre de Java et Assembly (différentes classes), le fait de devoir faire un projet C++ en 4 jours m'a jeté pour une boucle. Non seulement j'ai eu ma réponse, mais j'ai eu l'occasion de réfléchir aux différentes façons d'aborder un problème comme celui-ci. Impressionnant.

46
quandrum

Utilisez un infix_iterator:

// infix_iterator.h 
// 
// Lifted from Jerry Coffin's 's prefix_ostream_iterator 
#if !defined(INFIX_ITERATOR_H_) 
#define  INFIX_ITERATOR_H_ 
#include <ostream> 
#include <iterator> 
template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> > 
class infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag,void,void,void,void> 
{ 
    std::basic_ostream<charT,traits> *os; 
    charT const* delimiter; 
    bool first_elem; 
public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 
    infix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0), first_elem(true) 
    {} 
    infix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d), first_elem(true) 
    {} 
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
    { 
        // Here's the only real change from ostream_iterator: 
        // Normally, the '*os << item;' would come before the 'if'. 
        if (!first_elem && delimiter != 0) 
            *os << delimiter; 
        *os << item; 
        first_elem = false; 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator*() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++(int) { 
        return *this; 
    } 
};     
#endif 

L'utilisation serait quelque chose comme:

#include "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));
49
Jerry Coffin

Comme tout le monde a décidé de le faire avec des boucles while, je vais donner un exemple avec les boucles for.

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}
23
Jamie Wong

Dans un compilateur expérimental prêt pour C++ 17, vous pouvez utiliser std::experimental::ostream_joiner

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

Exemples en direct avec GCC 6.0 SVN et Clang 3.9 SVN

22
TemplateRex

Une approche courante consiste à imprimer le premier élément avant la boucle et à ne boucler que sur les éléments restants, en préimprimant une virgule avant chaque élément restant.

Alternativement, vous devriez être capable de créer votre propre flux qui maintient un état actuel de la ligne (avant endl) et place des virgules à l'endroit approprié.

EDIT: Vous pouvez également utiliser une boucle testée au milieu comme suggéré par T.E.D. Ce serait quelque chose comme:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

J'ai mentionné la méthode "imprimer le premier élément avant la boucle" en premier parce qu'elle garde le corps de la boucle très simple, mais que toutes les approches fonctionnent correctement.

17
Mark B

En supposant un flux de sortie vaguement normal, alors écrire une chaîne vide ne fait rien:

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}
14
Steve Jessop

Quelque chose comme ça?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}
7
Klark

Il y a beaucoup de solutions intelligentes, et trop nombreuses qui déforment le code au-delà de tout espoir de salut sans laisser le compilateur faire son travail.

La solution évidente, est à cas particulier la première itération:

bool first = true;
for (auto const& e: sequence) {
   if (first) { first = false; } else { out << ", "; }
   out << e;
}

C'est un modèle simple et mort qui:

  1. Ne brise pas la boucle: il est toujours évident que chaque élément sera itéré.
  2. Permet plus que la simple insertion d'un séparateur ou l'impression réelle d'une liste, car le bloc else et le corps de la boucle peuvent contenir des instructions arbitraires.

Ce n'est peut-être pas le code le plus efficace, mais la perte de performances potentielle d'une branche bien prédite risque fort d'être éclipsée par l'énorme monstre std::ostream::operator<<.

5
Matthieu M.

Ma méthode habituelle pour faire des séparateurs (dans n'importe quel langage) consiste à utiliser une boucle testée à mi-parcours. Le code C++ serait:

for (;;) {
   std::cout << *iter;
   if (++iter == keywords.end()) break;
   std::cout << ",";
}

(Remarque: une vérification if supplémentaire est nécessaire avant la boucle si les mots clés peuvent être vides)

La plupart des autres solutions présentées finissent par faire un test supplémentaire complet à chaque itération de boucle. Vous faites des E/S, alors le temps que cela prend n'est pas un problème énorme, mais cela choque ma sensibilité.

5
T.E.D.

Essaye ça:

typedef  std::vector<std::string>   Container;
typedef Container::const_iterator   CIter;
Container   data;

// Now fill the container.


// Now print the container.
// The advantage of this technique is that ther is no extra test during the loop.
// There is only one additional test !test.empty() done at the beginning.
if (!data.empty())
{
    std::cout << data[0];
    for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
    {
        std::cout << "," << *loop;
    }
}
3
Martin York

Il y a un petit problème avec l'opérateur ++ que vous utilisez.

Tu peux essayer:

if (++iter != keywords.end())
    out << ", ";
iter--;

De cette façon, ++ sera évalué avant de comparer l'itérateur avec keywords.end().

2
Pablo Santa Cruz

Je vous suggère simplement de changer le premier caractère à l'aide d'un lambda.

std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };                  

for (auto &k : keywords)
    std::cout << f() << k;
2
lewohart

En python, nous écrivons simplement:

print ", ".join(keywords)

alors pourquoi pas:

template<class S, class V>
std::string
join(const S& sep, const V& v)
{
  std::ostringstream oss;
  if (!v.empty()) {
    typename V::const_iterator it = v.begin();
    oss << *it++;
    for (typename V::const_iterator e = v.end(); it != e; ++it)
      oss << sep << *it;
  }
  return oss.str();
}

et puis juste l'utiliser comme:

cout << join(", ", keywords) << endl;

Contrairement à l'exemple python ci-dessus où " " est une chaîne et keywords doit être un itérable de chaînes, ici, dans cet exemple C++, le séparateur et keywords peuvent être tout ce qui peut être streamé, par exemple.

cout << join('\n', keywords) << endl;
2
Darko Veberic

Suivre devrait faire: -

 const std::vector<__int64>& a_setRequestId
 std::stringstream strStream;
 std::copy(a_setRequestId.begin(), a_setRequestId.end() -1, std::ostream_iterator<__int64>(strStream, ", "));
 strStream << a_setRequestId.back();
1
leoismyname

Je pense que cela devrait fonctionner

while (iter != keywords.end( ))
{

    out << *iter;
    iter++ ;
    if (iter != keywords.end( )) out << ", ";
}
1
Anycorn

pour éviter de placer une if dans la boucle, j'utilise ceci:

vector<int> keywords = {1, 2, 3, 4, 5};

if (!keywords.empty())
{
    copy(keywords.begin(), std::prev(keywords.end()), 
         std::ostream_iterator<int> (std::cout,", "));
    std::cout << keywords.back();
}

Cela dépend du type de vecteur, int, mais vous pouvez le supprimer avec un assistant.

1
Grim Fandango

J'utilise une petite classe d'aide pour cela:

class text_separator {
public:
    text_separator(const char* sep) : sep(sep), needsep(false) {}

    // returns an empty string the first time it is called
    // returns the provided separator string every other time
    const char* operator()() {
        if (needsep)
            return sep;
        needsep = true;
        return "";
    }

    void reset() { needsep = false; }

private:
    const char* sep;
    bool needsep;
};

Pour l'utiliser:

text_separator sep(", ");
for (int i = 0; i < 10; ++i)
    cout << sep() << i;
1
Ferruccio

Pourrait être comme ça ..

bool bFirst = true;
for (auto curr = keywords.begin();  curr != keywords.end(); ++curr) {
   std::cout << (bFirst ? "" : ", ") << *curr;
   bFirst = false;
}
1
JohnMcG

Utilisation de boost:

std::string add_str("");
const std::string sep(",");

for_each(v.begin(), v.end(), add_str += boost::lambda::ret<std::string>(boost::lambda::_1 + sep));

et vous obtenez une chaîne contenant le vecteur, délimité par des virgules.

EDIT: Pour supprimer la dernière virgule, il suffit de publier:

add_str = add_str.substr(0, add_str.size()-1);
1
Florin M

Peut utiliser des foncteurs:

#include <functional>

string getSeparatedValues(function<bool()> condition, function<string()> output, string separator)
{
    string out;
    out += output();
    while (condition())
        out += separator + output();
    return out;
}

Exemple:

if (!keywords.empty())
{
    auto iter = keywords.begin();
    cout << getSeparatedValues([&]() { return ++iter != keywords.end(); }, [&]() { return *iter; }, ", ") << endl;
}
0
Pat-Laugh

Celui-ci surcharge l'opérateur de flux. Oui, les variables globales sont mauvaises.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>

int index = 0;
template<typename T, template <typename, typename> class Cont>
std::ostream& operator<<(std::ostream& os, const Cont<T, std::allocator<T>>& vec)
{
    if (index < vec.size()) {
        if (index + 1 < vec.size())
            return os << vec[index++] << "-" << vec;
        else
            return os << vec[index++] << vec;
    } else return os;
}

int main()
{
    std::vector<int> nums(10);
    int n{0};
    std::generate(nums.begin(), nums.end(), [&]{ return n++; });
    std::cout << nums << std::endl;
}
0
user1508519

Si les valeurs sont std::strings, vous pouvez l'écrire correctement dans un style déclaratif avec range-v3

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<std::string> const vv = { "a","b","c" };

    auto joined = vv | view::join(',');

    std::cout << to_<std::string>(joined) << std::endl;
}

Pour les autres types qui doivent être convertis en chaîne, vous pouvez simplement ajouter une transformation appelant to_string.

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<int> const vv = { 1,2,3 };

    auto joined = vv | view::transform([](int x) {return std::to_string(x);})
                     | view::join(',');
    std::cout << to_<std::string>(joined) << std::endl;
}
0
Jens

Je voudrais aller avec quelque chose comme ça, une solution facile et devrait fonctionner pour tous les itérateurs.

int maxele = maxele = v.size() - 1;
for ( cur = v.begin() , i = 0; i < maxele ; ++i)
{
    std::cout << *cur++ << " , ";
}
if ( maxele >= 0 )
{
  std::cout << *cur << std::endl;
}
0
nsivakr

Une autre solution possible, qui évite une if

Char comma = '[';
for (const auto& element : elements) {
    std::cout.put(comma) << element;
    comma = ',';
}
std::cout.put(']');

Cela dépend de ce que vous faites dans votre boucle.

0
Matt Clarkson

Vous pouvez utiliser une boucle do, réécrire la condition de boucle pour la première itération et utiliser l'opérateur && de court-circuit et le fait qu'un flux valide est true.

auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
    out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;
0
Potatoswatter