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.
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, ","));
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;
}
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
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.
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 = ", "
}
Quelque chose comme ça?
while (iter != keywords.end())
{
out << *iter;
iter++;
if (iter != keywords.end()) cout << ", ";
}
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:
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<<
.
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é.
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;
}
}
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()
.
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;
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;
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();
Je pense que cela devrait fonctionner
while (iter != keywords.end( ))
{
out << *iter;
iter++ ;
if (iter != keywords.end( )) out << ", ";
}
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.
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;
Pourrait être comme ça ..
bool bFirst = true;
for (auto curr = keywords.begin(); curr != keywords.end(); ++curr) {
std::cout << (bFirst ? "" : ", ") << *curr;
bFirst = false;
}
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);
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;
}
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;
}
Si les valeurs sont std::string
s, 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;
}
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;
}
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.
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;