Pregunta Idioma (s) para "para cada uno, excepto el último" (o "entre cada par de elementos consecutivos") [duplicado]


Esta pregunta ya tiene una respuesta aquí:

Todos encuentran este problema en algún momento:

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

... y obtienes un separador adicional que no deseas al final. En algún momento no se está imprimiendo, pero, por ejemplo, realizando alguna otra acción, pero tal que acciones consecutivas del mismo tipo requieren alguna acción de separación, pero la última no.

Ahora, si trabajas con la vieja escuela para bucles y una matriz, harías

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

(o podría sacar un caso especial del último elemento del ciclo.) Si tiene algo que admite iteradores no destructivos, incluso si no conoce su tamaño, puede hacer:

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

No me gusta la estética de los dos últimos, y me gustan los bucles de rango. ¿Puedo obtener el mismo efecto que con los dos últimos, pero usando constructos más complejos de C ++ 11ish?


Para ampliar la cuestión más allá (por ejemplo, éste), Diré que también me gustaría no tener expresamente un caso especial como primer o último elemento. Es un "detalle de implementación" con el que no quiero que me molesten. Entonces, en imaginario-futuro-C ++, tal vez algo así como:

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

73
2018-02-12 21:51


origen


Respuestas:


Mi camino (sin una rama adicional) es:

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

70
2018-02-12 22:07



Tú sabes Dispositivo 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;
}

(Vivir)

Con suerte no arruiné el día de todos. Si no quieres, entonces Nunca utilizar esta.

(murmullo) Lo siento mucho ...


El dispositivo que maneja contenedores vacíos (rangos):

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

Prueba en vivo:

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

23
2018-02-13 00:31



Excluir un elemento final de la iteración es el tipo de cosa que la propuesta Ranges está diseñada para facilitar. (Tenga en cuenta que hay mejores formas de resolver la tarea específica de unir cadenas, separar un elemento de la iteración solo crea más casos especiales de los que preocuparse, como cuando la colección ya estaba vacía).

Mientras esperamos un paradigma Rangos estandarizado, podemos hacerlo con el ranged-for existente con una pequeña clase de ayuda.

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

y ahora puedes escribir

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

Manifestación: http://rextester.com/MFH77611

por skip_last que funciona con ranged-for, se necesita un iterador bidireccional, para skip_first es suficiente tener un iterador Adelante.


23
2018-02-12 22:03



No conozco ningún modismo especial para esto. Sin embargo, prefiero un caso especial al primero y luego realizo la operación en los elementos restantes.

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

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


13
2018-02-12 21:54



Por lo general, lo hago de la manera opuesta:

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

13
2018-02-12 21:57



Me gustan las estructuras de control simple.

if (first == last) return;

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

Dependiendo de su gusto, puede poner el incremento y la prueba en una sola línea:

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

7
2018-02-13 03:43



No creo que puedas evitar tener un caso especial algun lado... Por ejemplo, Boost's Biblioteca de algoritmos de cadenas tiene un unirse algoritmo. Si nos fijamos en su implementación, verá un caso especial para el primer elemento (sin delimitador anterior) y se agregará un delimitador antes de cada elemento subsiguiente.


5
2018-02-12 22:39



Podría definir una función para_ejemplo_y_unirse que tome dos funtores como argumento. El primer functor funciona con cada elemento, el segundo funciona con cada par de elementos adyacentes:

#include <iostream>
#include <vector>

template <typename Iter, typename FEach, typename FJoin>
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<int> values = { 1, 2, 3, 4, 5 };
    for_each_and_join(values.begin(), values.end()
    ,  [](auto v) { std::cout << v; }
    ,  [](auto, auto) { std::cout << ","; }
    );
}

Ejemplo en vivo: http://ideone.com/fR5S9H


5
2018-02-13 08:44



int a[3] = {1,2,3};
int size = 3;
int i = 0;

do {
    std::cout << a[i];
} while (++i < size && std::cout << ", ");

Salida:

1, 2, 3 

El objetivo es usar el camino && es evaluado Si la primera condición es verdadera, evalúa la segunda. Si no es verdad, entonces la segunda condición se omite.


5
2018-02-13 15:14