lenguaje - metodos de ordenamiento en c++ codigo fuente
¿El bucle ''for'' basado en rango deprecia muchos algoritmos simples? (10)
Solución de Algoritmo:
std::generate(numbers.begin(), numbers.end(), rand);
Solución for-loop basada en rango:
for (int& x : numbers) x = rand();
¿Por qué querría usar los más detallados std::generate
over-based-for-loops en C ++ 11?
En mi opinión, el ciclo manual, aunque podría reducir la verbosidad, carece de reabiedad:
for (int& x : numbers) x = rand();
No usaría este ciclo para inicializar 1 el rango definido por números , porque cuando lo miro, me parece que está iterando sobre un rango de números, pero en realidad no lo hace (en esencia), es decir, en lugar de leyendo desde el rango, está escribiendo en el rango.
La intención es mucho más clara cuando usas std::generate
.
1. Inicializar en este contexto significa dar valor significativo a los elementos del contenedor.
Hay algunas cosas que no se pueden hacer (simplemente) con bucles basados en rangos que los algoritmos que toman iteradores como entrada pueden hacerlo. Por ejemplo, con std::generate
:
Llene el contenedor hasta el limit
(excluido, el limit
es un iterador válido en numbers
) con variables de una distribución y el resto con variables de otra distribución.
std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);
Los algoritmos basados en iterador le dan un mejor control en el rango en el que está operando.
La primera versión
std::generate(numbers.begin(), numbers.end(), rand);
nos dice que quieres generar una secuencia de valores.
En la segunda versión, el lector tendrá que descubrirlo por sí mismo.
Guardar en tipeo generalmente no es óptimo, ya que generalmente se pierde en el tiempo de lectura. La mayoría del código se lee mucho más de lo que se escribe.
Mi respuesta sería tal vez y no. Si estamos hablando de C ++ 11, entonces tal vez (más como no). Por ejemplo, std::for_each
es realmente molesto de usar incluso con lambdas:
std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
// do stuff with x
});
Pero usar el rango basado en es mucho mejor:
for (auto& x : c)
{
// do stuff with x
}
Por otro lado, si estamos hablando de C ++ 1y, entonces yo diría que no, los algoritmos no estarán obsoletos por el rango basado en. En el comité estándar de C ++ hay un grupo de estudio que está trabajando en una propuesta para agregar rangos a C ++, y también se está trabajando en lambdas polimórficas. Los rangos eliminarían la necesidad de usar un par de iteradores y el lambda polimórfico le permitiría no especificar el tipo de argumento exacto de lambda. Esto significa que std::for_each
podría usarse así (no lo tome como un hecho difícil, es exactamente como son los sueños hoy):
std::for_each(c.range(), [](x)
{
// do stuff with x
});
Para el caso particular de std::generate
, estoy de acuerdo con las respuestas anteriores sobre legibilidad / problema de intención. std :: generate me parece una versión más clara. Pero admito que esto es, en cierto modo, una cuestión de gusto.
Dicho esto, tengo otra razón para no tirar el algoritmo std :: hay ciertos algoritmos que están especializados para algunos tipos de datos.
El ejemplo más simple sería std::fill
. La versión general se implementa como un bucle for en el rango proporcionado, y esta versión se usará al crear la instancia de la plantilla. Pero no siempre. Por ejemplo, si le proporciona un rango que es un std::vector<int>
- a menudo llamará a memset
bajo el capó, produciendo un código mucho más rápido y mejor.
Así que estoy tratando de jugar una carta de eficiencia aquí.
Su ciclo escrito a mano podría ser tan rápido como una versión de std :: algorithm, pero difícilmente puede ser más rápido. Y más que eso, el algoritmo std :: puede estar especializado para contenedores y tipos particulares y se realiza bajo la interfaz limpia de STL.
Personalmente, mi lectura inicial de:
std::generate(numbers.begin(), numbers.end(), rand);
es "estamos asignando a todo en un rango. El rango son los numbers
. Los valores asignados son aleatorios".
Mi lectura inicial de:
for (int& x : numbers) x = rand();
es "estamos haciendo algo para todo en un rango. El rango son los numbers
. Lo que hacemos es asignar un valor aleatorio".
Esos son bastante similares, pero no idénticos. Una razón plausible por la que podría querer provocar la primera lectura es porque creo que el hecho más importante sobre este código es que se lo asigna al rango. Entonces está tu "por qué querría ...". Utilizo generate
porque en C ++ std::generate
significa "asignación de rango". Como por cierto, std::copy
, la diferencia entre los dos es a lo que estás asignando.
Sin embargo, hay factores de confusión. Los bucles basados en rangos tienen una forma inherentemente más directa de expresar que el rango son los numbers
, que los algoritmos basados en iteradores. Es por eso que las personas trabajan en bibliotecas de algoritmos basados en rangos: boost::range::generate(numbers, rand);
se ve mejor que la versión std::generate
.
En int&
, int&
en tu bucle for basado en rango es una arruga. Qué pasa si el tipo de valor del rango no es int
, entonces estamos haciendo algo sutilmente sutil aquí que depende de que sea convertible a int&
, mientras que el código de generate
solo depende de que el retorno de rand
sea asignable al elemento. Incluso si el tipo de valor es int
, aún podría detenerme a pensar si lo es o no. De ahí el auto
, que difiere de pensar en los tipos hasta que veo lo que se asigna - con auto &x
digo "tomar una referencia al elemento del rango, cualquiera que sea el tipo que pueda tener". De vuelta en C ++ 03, los algoritmos (porque son plantillas de funciones) eran la forma de ocultar tipos exactos, ahora son una forma.
Creo que siempre ha sido el caso que los algoritmos más simples tienen solo un beneficio marginal sobre los bucles equivalentes. Los bucles basados en rangos mejoran los bucles (principalmente al eliminar la mayor parte del texto repetitivo, aunque hay un poco más para ellos que eso). Entonces, los márgenes se ajustan más y tal vez cambies de opinión en algunos casos específicos. Pero todavía hay una diferencia de estilo allí.
Range-based for-loop es solo eso. Hasta que, por supuesto, el estándar se cambia.
Algoritmo es una función. Una función que pone algunos requisitos en sus parámetros. Los requisitos están redactados en un estándar para permitir, por ejemplo, la implementación que aprovecha todos los hilos de ejecución disponibles y acelerará automáticamente.
Si el bucle for está basado o no en el rango, no hace ninguna diferencia, solo simplifica el código dentro del paréntesis. Los algoritmos son más claros porque muestran la intención .
Una cosa que debe notarse es que un algoritmo expresa lo que se hace, no cómo.
El lazo basado en rangos incluye la forma en que se hacen las cosas: comience con el primero, aplique y vaya al siguiente elemento hasta el final. Incluso un algoritmo simple podría hacer las cosas de manera diferente (al menos algunas sobrecargas para contenedores específicos, sin siquiera pensar en el horrible vector), y al menos la manera en que se hace no es el negocio del escritor.
Para mí esa es la gran diferencia, encapsula tanto como puedas, y eso justifica la oración cuando puedas, utiliza algoritmos.
En mi opinión, el elemento eficaz 43 de STL: "Prefiere llamadas de algoritmo a bucles escritos a mano". sigue siendo un buen consejo.
Normalmente escribo funciones de contenedor para deshacerme del infierno de begin()
/ end()
. Si haces eso, tu ejemplo se verá así:
my_util::generate(numbers, rand);
Creo que supera al rango basado en el bucle tanto en la comunicación del intento como en la legibilidad.
Habiendo dicho eso, debo admitir que en C ++ 98 algunas llamadas al algoritmo STL arrojaron un código inexpresable y el siguiente "Preferimos llamadas de algoritmo a bucles escritos a mano" no parecía una buena idea. Afortunadamente, las lambdas han cambiado eso.
Considere el siguiente ejemplo de Herb Sutter: Lambdas, Lambdas Everywhere .
Tarea: Encuentre el primer elemento en v que sea > x
y < y
.
Sin lambdas:
auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );
Con lambda
auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );