79 votes

C++ langage(s) pour "pour chacun, sauf le dernier" (ou "entre chaque consécutives paire d'éléments")

Tout le monde rencontre ce problème à un certain point:

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

... et vous obtenez un supplément de séparateur que vous ne voulez pas à la fin. Parfois il n'est pas l'impression, mais, disons, d'effectuer une autre action, mais telles que les actions consécutives du même type nécessitent quelques séparateur de l'action, mais le dernier ne l'est pas.

Maintenant, si vous travaillez avec de la vieille école pour les boucles et un tableau, vous le feriez

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

(ou vous pouvez cas le dernier élément de la boucle.) Si vous avez quelque chose qui admet la non-destructive des itérateurs, même si vous ne savez 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 de ces deux derniers, et comme variaient pour les boucles. Puis-je obtenir le même effet qu'avec les deux derniers, mais en utilisant plus chic C++11ish constructions?

Edit:

Pour élargir la question plus loin (au-delà, de dire, de cette seule), je vais dire que je n'aime pas expressément ont cas le premier ou le dernier élément. C'est un "détail de l'implémentation" que je ne veux pas être dérangé. Donc, dans l'imaginaire-C++, peut-être quelque chose comme:

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

81voto

Jarod42 Points 15729

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

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

25voto

Ben Voigt Points 151460

L'exclusion d'un élément de fin de l'itération est le genre de chose qui va proposition est conçu pour rendre facile. (Notez qu'il existe de meilleures façons de résoudre la tâche spécifique de la chaîne de rejoindre, la rupture d'un élément à l'arrêt de l'itération juste qui crée le plus de cas particuliers à s'inquiéter, comme lors de la collecte était déjà vide.)

Alors que nous attendons pour une standardisé Plages de paradigme, nous pouvons le faire avec l'existant allaient-avec une petite aide de la classe.

template<typename T> struct trim_last
{
    T& inner;

    friend auto begin( const trim_last& outer )
    { using std::begin;
      return begin(outer.inner); }

    friend auto end( const trim_last& outer )
    { using std::end;
      auto e = end(outer.inner); if(e != begin(outer)) --e; return e; }
};

template<typename T> trim_last<T> 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 distance, un itérateur Bidirectionnel est nécessaire, pour similaires skip_first , il suffit d'avoir un Avant itérateur.

25voto

Daniel Jour Points 6551

Savez-vous Duff appareil?

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

(Live)

Heureusement que je n'ai pas la ruine de tous les jours. Si vous ne voulez pas soit alors de ne jamais utiliser cela.

(mumble) je suis tellement désolé ...


L'appareil de manutention des conteneurs vides (va):

template<typename Iterator, typename Fn1, typename Fn2>
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;
}

13voto

James Adkison Points 6334

Je ne sais pas du tout spécial idiomes pour cela. Cependant, je préfère cas spécial à la première, puis effectuer l'opération sur le reste des éléments.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> 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"

13voto

Matteo Italia Points 53117

Généralement, je fais le chemin inverse:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X