c++ - puedo - no me funcionan los hashtags en instagram 2018
C++ Conmutar declaraciĆ³n larga o buscar con un mapa? (11)
En mi aplicación C ++, tengo algunos valores que actúan como códigos para representar otros valores. Para traducir los códigos, he estado debatiendo entre utilizar una instrucción switch o un mapa stl. El cambio se vería así:
int code;
int value;
switch(code)
{
case 1:
value = 10;
break;
case 2:
value = 15;
break;
}
El mapa sería un stl::map<int, int>
y la traducción sería una búsqueda simple con el código utilizado como valor clave.
¿Cuál es mejor / más eficiente / más limpio / aceptado? ¿Por qué?
Creo que el código generado de la estructura de la caja de conmutación puede crecer bastante, si la cantidad de "códigos" aumenta, en cuyo caso creo que el stl :: map es más apropiado.
Digo mapa si todo lo que está haciendo es asignar un valor. Mi razón es que es extensible sin cambiar el código, como no lo es su declaración de cambio.
por cierto, ¿qué tal una enum ?
La declaración de cambio sería más rápida, pero si esto no está en el cuello de botella de rendimiento de su aplicación, realmente no debería preocuparse por eso.
Vaya por lo que hace que su código sea más fácil de mantener a largo plazo.
Su muestra es demasiado corta para hacer una llamada significativa en ese sentido.
Más bien depende de cuáles son los códigos y cuántos hay. Los buenos compiladores tienen varios trucos que utilizan para optimizar las sentencias de cambio, algunas de las cuales no emplearán con enunciados directos si / entonces. La mayoría son lo suficientemente brillantes como para hacer operaciones matemáticas simples o usar tablas de búsqueda / salto para los casos 0, 1, 2 o caso 3, 6, 9, por ejemplo.
Por supuesto, algunos no, y muchos son fácilmente frustrados por conjuntos de valores inusuales o irregulares. Además, si el código para manejar varios casos parece muy similar, cortar y pegar puede llevar a problemas de mantenimiento. Si tiene muchos códigos pero se pueden dividir algorítmicamente en grupos, puede considerar varias declaraciones de conmutador / anidadas, por ejemplo, en lugar de:
switch (code) {
case 0x0001: ...
case 0x0002: ...
...
case 0x8001: ...
case 0x8002: ...
...
}
Puede usar:
if (code & 0x8000) {
code &= ~0x8000;
switch (code) {
case 0x0001: ... // actually 0x8001
case 0x0002: ... // actually 0x8002
...
}
}
else {
switch (code) {
case 0x0001: ...
case 0x0002: ...
...
}
}
Muchos intérpretes de idiomas decodifican los códigos de operación de esta manera, ya que un código de operación de un solo byte puede tener información adicional empaquetada en varios bits, y la transcripción de todas las combinaciones posibles y sus manejadores sería repetitiva y frágil. Por otro lado, la excesiva manipulación de bits puede anular cualquier optimización por parte del compilador y ser contraproducente.
A menos que esté seguro de que se trata de un cuello de botella en el rendimiento real, evitaría una optimización prematura: hacerlo de cualquier manera que le parezca bastante sólido y rápido de implementar. Como y si su aplicación se ejecuta muy lentamente, perfórela y optimícela según corresponda.
Normalmente sugeriría un mapa o una matriz de búsqueda o tal vez incluso un monstruo de búsqueda híbrido (suponiendo que esté optimizando la velocidad o el tamaño del código en lugar de la legibilidad, por supuesto), pero vale la pena señalar que las nuevas versiones de GCC son inteligentes suficiente para reemplazar las asignaciones de switch / case como esta para las tablas de búsqueda. Si bien esto no es tan bueno para las teclas totalmente dispersas, puede ser si estas están agrupadas como: 0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 193, 194, 195, 196, 197, 198 ...
Por supuesto, si usted pudiera encontrar algún aritmático para hacer la conversión, incluso mejor (generalmente). Nunca pases por alto el cambio, el cambio de endianness o las matemáticas más tradicionales cuando hagas algo como esto.
Personalmente, usaría el mapa, ya que su uso implica una búsqueda de datos; el uso de un interruptor generalmente indica una diferencia en el comportamiento del programa. Además, modificar la asignación de datos es más fácil con un mapa que con un interruptor.
Si el rendimiento es un problema real, la creación de perfiles es la única forma de obtener una respuesta útil. Un cambio puede no ser más rápido si las predicciones erróneas de las ramas ocurren con la frecuencia suficiente.
Otro enfoque para pensar sobre esto es si no tendría más sentido combinar el código y el valor asociado en una estructura de datos, especialmente si el rango de códigos y valores es estático:
struct Code { int code; int value; };
Code c = ...
std::cout << "Code " << c.code << ", value " << c.value << std::end;
Si puedes usar tr1, puedes usar unordered_map para hacer hash de los valores (los valores de hash también pueden ser muy rápidos), lo que debería hacer que la mayoría de las búsquedas sean constantes en el tiempo.
Sin embargo, a menos que tenga datos de perfil para indicar que este es un cuello de botella en su programa, codifíquelo en el enfoque que tenga más sentido desde el punto de vista del diseño.
Si sus códigos son lo suficientemente contiguos y su rango lo permite, sería mucho mejor que usarlos con una matriz simple y antigua, algo así como
int lookup[] = {-1, 10, 15, -1 222};
entonces la instrucción de cambio puede ser reescrita tan simplemente como
valor = búsqueda [código];
todas las demás opciones introducen costo adicional hasta cierto punto.
Soy parcial en las tablas de búsqueda, porque las sentencias de cambio inusualmente largas me parecen confundir una separación entre código y datos. También creo que las tablas se prestan mejor para futuros cambios y mantenimiento.
Todo IMHO, por supuesto.
Sugiero usar una tabla estática, constante, de pares. Esta es otra forma de la tabla de búsqueda:
struct Table_Entry
{
int code;
int value;
};
static const Table_Entry lookup_table[] =
{
{1, 10},
{2, 15},
{3, 13},
};
static const unsigned int NUM_TABLE_ENTRIES =
sizeof(lookup_table) / sizeof(lookup_table[0]);
Un beneficio de esto es que la tabla se genera en tiempo de compilación, a diferencia de std::map
que debe inicializarse durante el tiempo de ejecución. Si las cantidades son grandes, puede usar std::lower_bound
para encontrar la entrada, siempre que la tabla esté ordenada.
Otro beneficio es que esta técnica es impulsada por datos. Los datos pueden cambiar sin cambios en el motor de búsqueda. Los cambios en el código o proceso pueden requerir pruebas de regresión graves, pero los cambios en los datos pueden no serlo; YMMV.
Esto es similar a lo que podría generar un compilador.
- Lee los enteros en una matriz / vector
- ordenar la matriz / vector
- use bsearch en la matriz subyacente