c++ c++11 idioms separator

c++ - Idioma(s) para "para cada uno excepto el último"(o "entre cada par de elementos consecutivos")



c++11 idioms (12)

Todos se encuentran con este problema en algún momento:

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

... y obtienes un separador extra que no quieres al final. En algún momento no se imprime, pero, por ejemplo, se realiza alguna otra acción, pero las 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 poner en un caso especial el último elemento fuera del ciclo). Si tiene algo que admite iteradores no destructivos, incluso si no conoce su tamaño, puede hacer lo siguiente:

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 gusta la variedad de bucles. ¿Puedo obtener el mismo efecto que con los dos últimos pero usando construcciones más elegantes de C ++ 11ish?

Para ampliar aún más la pregunta (más allá de, digamos, esta ), diré que también me gustaría no tener expresamente en caso especial el primer o el último elemento. Ese es un "detalle de implementación" del que no quiero que me molesten. Entonces, en imaginary-future-C ++, tal vez algo como:

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


¿Conoces el 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; }

(Live)

Ojalá no arruinara el día de todos. Si no quieres tampoco, nunca uses esto.

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


Aquí hay un pequeño truco que me gusta usar:

Para objetos iterables bidireccionalmente: 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); };

¿Usando el ternario ? operador item.end()-1 la posición actual del iterador con el item.end()-1 llamada. Dado que el iterador devuelto por item.end() refiere a la posición después del último elemento, lo decrementamos una vez para obtener nuestro último elemento real.

Si este elemento no es el último elemento en el iterable, devolvemos nuestro separador (definido en otra parte), o si es el último elemento, devolvemos una cadena vacía.

Para iterables de una sola dirección (probado con 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); };

Aquí, estamos reemplazando la condición ternaria anterior con una llamada a std :: distance usando la ubicación actual del iterador y el final del iterable.

Tenga en cuenta que esta versión funciona tanto con iterables bidireccionales como con iterables de una sola dirección.

EDITAR: Me doy cuenta de que no te gustan las iteraciones de tipo .begin() y .end() , pero si estás buscando mantener la cuenta regresiva LOC, probablemente tendrás que evitar la iteración basada en rango en este caso.

El "Truco" es simplemente envolver la lógica de comparación dentro de una sola expresión ternaria, si su lógica de comparación es relativamente simple.


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

Mientras esperamos un paradigma de rangos estandarizado, podemos hacerlo con los rangos existentes con una pequeña clase auxiliar.

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

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

Para skip_last que funciona con ranged-for, se necesita un iterador bidireccional, para skip_first similar es suficiente tener un iterador Forward.


Me gusta la función boost::join . Entonces, para un comportamiento más general, desea una función que se llame para cada par de elementos y que pueda tener un estado persistente. Lo usarías como una llamada de función con una lambda:

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

¡Ahora puede volver a un bucle for basado for rango normal utilizando filtros de rango !

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

En esta idea, pair se establece en un par de elementos adyacentes de la colección original. Si tiene "abcde", en la primera iteración se le da primero = ''a'' y segundo = ''b''; la próxima vez a través de first = ''b'' y second = ''c''; etc.

Puede usar un enfoque de filtro similar para preparar una tupla que etiqueta cada elemento de iteración con una enumeración para / first / middle / last / iteration y luego hacer un cambio dentro del ciclo.

Para simplemente dejar el último elemento, use un filtro de rango para todo menos el último. No sé si eso ya está en Boost.Range o qué se puede proporcionar con Rangev3 en progreso, pero ese es el enfoque general para hacer que el bucle regular haga trucos y lo haga "ordenado".


Me gustan las estructuras de control simples.

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


Mi camino (sin rama adicional) es:

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


No conozco modismos especiales para esto. Sin embargo, prefiero el caso especial primero y luego realizar 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"


No creo que pueda evitar tener un caso especial en alguna parte ... Por ejemplo, la biblioteca de algoritmos de cadenas de Boost tiene un algoritmo de join . Si observa su implementation , verá un caso especial para el primer elemento (sin delimitador en curso) y luego se agrega un delimitador antes de cada elemento posterior.


No sé acerca de "idiomático", pero C ++ 11 proporciona std::prev y std::next funciones para iteradores bidireccionales.

int main() { vector<int> items = {0, 1, 2, 3, 4}; string 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())); } }


Normalmente lo hago de la manera opuesta:

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


Podría definir una función para cada uno y unir que tome dos functores 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


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 utilizar la forma en que se evalúa && . Si la primera condición es verdadera, evalúa la segunda. Si no es cierto, se omite la segunda condición.