Idiome (s) pour «pour chaque sauf le dernier» (ou «entre chaque paire d’éléments consécutive»)

Tout le monde rencontre ce problème à un moment donné:

for(const auto& item : items) { cout << item << separator; } 

… et vous obtenez un séparateur supplémentaire que vous ne voulez pas à la fin. Parfois, cela n’imprime pas, mais, par exemple, exécute une autre action, mais des actions consécutives du même type nécessitent une action de séparation, mais pas la dernière.

Maintenant, si vous travaillez avec old-school pour les boucles et un tableau, vous feriez

 for(int i = 0; i < num_items; i++) cout << items[i]; if (i < num_items - 1) { cout << separator; } } 

(ou vous pourriez spécial-caser le dernier élément de la boucle.) Si vous avez quelque chose qui admet des iterators non destructifs, même si vous ne connaissez pas sa taille, vous pouvez le faire:

 for(auto it = items.cbegin(); it != items.cend(); it++) { cout << *it; if (std::next(it) != items.cend()) { cout << separator; } } 

Je n’aime pas l’esthétique des deux derniers, et j’aime bien la scope des boucles. Puis-je obtenir le même effet qu’avec les deux derniers mais en utilisant des constructions C ++ 11ish plus sophistiquées?


Pour approfondir la question (au-delà, disons, celle-ci ), je dirai que je voudrais également ne pas avoir expressément le cas spécial du premier ou du dernier élément. C’est un “détail d’implémentation” avec lequel je ne veux pas être dérangé. Donc, dans imaginary-future-C ++, peut-être quelque chose comme:

 for(const auto& item : items) { cout << item; } and_between { cout << separator; } 

Ma façon (sans twig supplémentaire) est:

 const auto separator = "WhatYouWantHere"; const auto* sep = ""; for(const auto& item : items) { std::cout < < sep << item; sep = separator; } 

Connaissez-vous l’appareil de Duff ?

 int main() { int const items[] = {21, 42, 63}; int const * item = items; int const * const end = items + sizeof(items) / sizeof(items[0]); // the device: switch (1) { case 0: do { cout < < ", "; default: cout << *item; ++item; } while (item != end); } cout << endl << "I'm so sorry" << endl; return 0; } 

(Vivre)

J'espère que je n'ai pas gâché la journée de tout le monde. Si vous ne voulez pas non plus, ne l' utilisez jamais .

(marmonne) Je suis vraiment désolé ...


Le périphérique qui gère les conteneurs vides (plages):

 template void for_the_device(Iterator from, Iterator to, Fn1 always, Fn2 butFirst) { switch ((from == to) ? 1 : 2) { case 0: do { butFirst(*from); case 2: always(*from); ++from; } while (from != to); default: // reached directly when from == to break; } } 

Test en direct :

 int main() { int const items[] = {21, 42, 63}; int const * const end = items + sizeof(items) / sizeof(items[0]); for_the_device(items, end, [](auto const & i) { cout < < i;}, [](auto const & i) { cout << ", ";}); cout << endl << "I'm (still) so sorry" << endl; // Now on an empty range for_the_device(end, end, [](auto const & i) { cout << i;}, [](auto const & i) { cout << ", ";}); cout << "Incredibly sorry." << endl; return 0; } 

L’exclusion d’un élément de fin de l’itération est le type de chose que la proposition de gammes est conçue pour faciliter. (Notez qu’il existe de meilleurs moyens de résoudre la tâche spécifique de la jointure de chaînes, la suppression d’un élément de l’itération crée simplement plus de cas particuliers dont vous devez vous préoccuper, par exemple lorsque la collection est déjà vide.)

Pendant que nous attendons un paradigme de plages standardisé, nous pouvons le faire avec les classes à distance existantes avec une petite classe d’assistance.

 template struct sortingm_last { T& inner; friend auto begin( const sortingm_last& outer ) { using std::begin; return begin(outer.inner); } friend auto end( const sortingm_last& outer ) { using std::end; auto e = end(outer.inner); if(e != begin(outer)) --e; return e; } }; template sortingm_last skip_last( T& inner ) { return { inner }; } 

et maintenant vous pouvez écrire

 for(const auto& item : skip_last(items)) { cout < < item << separator; } 

Démo: http://rextester.com/MFH77611

Pour skip_last qui fonctionne avec skip_last -for, un iterator bidirectionnel est nécessaire. Pour un skip_first similaire, il suffit d'avoir un iterator Forward.

Je ne connais aucun idiome spécial pour cela. Cependant, je préfère le cas particulier le premier et ensuite effectuer l’opération sur les éléments restants.

 #include  #include  int main() { std::vector values = { 1, 2, 3, 4, 5 }; std::cout < < "\""; if (!values.empty()) { std::cout << values[0]; for (size_t i = 1; i < values.size(); ++i) { std::cout << ", " << values[i]; } } std::cout << "\"\n"; return 0; } 

Sortie: "1, 2, 3, 4, 5"

En règle générale, je le fais dans le sens inverse:

 bool first=true; for(const auto& item : items) { if(!first) cout<  

J’aime les structures de contrôle simples.

 if (first == last) return; while (true) { std::cout < < *first; ++first; if (first == last) break; std::cout << separator; } 

En fonction de vos goûts, vous pouvez mettre l’incrément et tester en une seule ligne:

 ... while (true) { std::cout < < *first; if (++first == last) break; std::cout << separator; } 

Je ne pense pas que vous puissiez avoir un cas particulier quelque part … Par exemple, la bibliothèque d’algorithmes de chaîne de Boost a un algorithme de jointure . Si vous regardez son implémentation , vous verrez un cas particulier pour le premier élément (pas de délimiteur en cours) et un séparateur est ajouté avant chaque élément suivant.

Vous pouvez définir une fonction for_each_and_join qui prend deux foncteurs comme argument. Le premier foncteur fonctionne avec chaque élément, le second fonctionne avec chaque paire d’éléments adjacents:

 #include  #include  template  void for_each_and_join(Iter iter, Iter end, FEach&& feach, FJoin&& fjoin) { if (iter == end) return; while (true) { feach(*iter); Iter curr = iter; if (++iter == end) return; fjoin(*curr, *iter); } } int main() { std::vector values = { 1, 2, 3, 4, 5 }; for_each_and_join(values.begin(), values.end() , [](auto v) { std::cout < < v; } , [](auto, auto) { std::cout << ","; } ); } 

Exemple en direct: http://ideone.com/fR5S9H

 int a[3] = {1,2,3}; int size = 3; int i = 0; do { std::cout < < a[i]; } while (++i < size && std::cout << ", "); 

Sortie:

 1, 2, 3 

Le but est d'utiliser la façon dont && est évaluée. Si la première condition est vraie, elle évalue la seconde. Si ce n'est pas vrai, la deuxième condition est ignorée.

Je ne connais pas “idiomatique”, mais C ++ 11 fournit des fonctions std::prev et std::next pour les iterators bidirectionnels.

 int main() { vector items = {0, 1, 2, 3, 4}; ssortingng separator(","); // Guard to prevent possible segfault on prev(items.cend()) if(items.size() > 0) { for(auto it = items.cbegin(); it != prev(items.cend()); it++) { cout < < (*it) << separator; } cout << (*prev(items.cend())); } } 

J’aime la fonction boost::join . Donc, pour un comportement plus général, vous voulez une fonction appelée pour chaque paire d’éléments et pouvant avoir un état persistant. Vous l’utiliseriez comme un appel de fonction avec un lambda:

 foreachpair (range, [](auto left, auto right){ whatever }); 

Maintenant, vous pouvez revenir à une boucle basée for plage en utilisant des filtres de plage !

 for (auto pair : collection|aspairs) { Do-something_with (pair.first); } 

Dans cette idée, la pair est définie sur une paire d’éléments supplémentaires de la collection d’origine. Si vous avez “abcde”, à la première itération, vous recevez first = ‘a’ et second = ‘b’; la prochaine fois à travers first = ‘b’ et second = ‘c’; etc.

Vous pouvez utiliser une approche de filtrage similaire pour préparer un tuple qui balise chaque élément d’itération avec une énumération pour / first / middle / last / iteration, puis effectuez un changement dans la boucle.

Pour supprimer simplement le dernier élément, utilisez un filtre d’intervalle pour tout-sauf. Je ne sais pas si c’est déjà dans Boost.Range ou ce qui pourrait être fourni avec Rangev3 en cours, mais c’est l’approche générale pour que la boucle normale fasse des tours et la rende “soignée”.

Voici un petit truc que j’aime bien utiliser:

Pour les objects à itération bidirectionnelle: for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout < < *it << (it == items.end()-1 ? "" : sep); }; for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout < < *it << (it == items.end()-1 ? "" : sep); };

En utilisant le ternaire ? Opérateur Je compare la position actuelle de l'iterator à l' item.end()-1 . Puisque l'iterator renvoyé par item.end() fait référence à la position après le dernier élément, nous le décrémentons une fois pour obtenir notre dernier élément réel.

Si cet élément n'est pas le dernier élément de l'itérable, nous retournons notre séparateur (défini ailleurs), ou si c'est le dernier élément, nous retournons une chaîne vide.

Pour les itérations à sens unique (testées avec std :: forward_list): for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout < < *it << (std::distance( it, items.end() ) == 1 ? "" : sep); }; for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout < < *it << (std::distance( it, items.end() ) == 1 ? "" : sep); };

Ici, nous remplaçons la condition ternaire précédente par un appel à std :: distance en utilisant l'emplacement de l'iterator actuel et la fin de l'itérable.

Notez que cette version fonctionne à la fois avec les itérations bidirectionnelles et les itérations à sens unique.

EDIT: Je me rends compte que vous n'aimez pas les itérations de type .begin() et .end() , mais si vous souhaitez conserver le compte à rebours, vous devrez probablement éviter une itération basée sur la scope dans ce cas.

La "astuce" consiste simplement à encapsuler la logique de comparaison dans une seule expression ternaire, si votre logique de comparaison est relativement simple.