switch sentencia programacion lenguaje instruccion else ejemplos condicionales c++ if-statement

c++ - sentencia - ¿Cómo evitar if/else if chain al clasificar un rumbo en 8 direcciones?



sentencia if (10)

Aunque las variantes propuestas basadas en una tabla de búsqueda para angle / 30 son probablemente preferibles, aquí hay una alternativa que utiliza una búsqueda binaria codificada para minimizar el número de comparaciones.

static Car::EDirection directionFromAngle( int angle ) { if( angle <= 210 ) { if( angle > 120 ) return angle > 150 ? Car::EDirection::LEFT : Car::EDirection::UP_LEFT; if( angle > 30 ) return angle > 60 ? Car::EDirection::UP : Car::EDirection::UP_RIGHT; } else // > 210 { if( angle <= 300 ) return angle > 240 ? Car::EDirection::DOWN : Car::EDirection::DOWN_LEFT; if( angle <= 330 ) return Car::EDirection::DOWN_RIGHT; } return Car::EDirection::RIGHT; // <= 30 || > 330 }

Tengo el siguiente código:

if (this->_car.getAbsoluteAngle() <= 30 || this->_car.getAbsoluteAngle() >= 330) this->_car.edir = Car::EDirection::RIGHT; else if (this->_car.getAbsoluteAngle() > 30 && this->_car.getAbsoluteAngle() <= 60) this->_car.edir = Car::EDirection::UP_RIGHT; else if (this->_car.getAbsoluteAngle() > 60 && this->_car.getAbsoluteAngle() <= 120) this->_car.edir = Car::EDirection::UP; else if (this->_car.getAbsoluteAngle() > 120 && this->_car.getAbsoluteAngle() <= 150) this->_car.edir = Car::EDirection::UP_LEFT; else if (this->_car.getAbsoluteAngle() > 150 && this->_car.getAbsoluteAngle() <= 210) this->_car.edir = Car::EDirection::LEFT; else if (this->_car.getAbsoluteAngle() > 210 && this->_car.getAbsoluteAngle() <= 240) this->_car.edir = Car::EDirection::DOWN_LEFT; else if (this->_car.getAbsoluteAngle() > 240 && this->_car.getAbsoluteAngle() <= 300) this->_car.edir = Car::EDirection::DOWN; else if (this->_car.getAbsoluteAngle() > 300 && this->_car.getAbsoluteAngle() <= 330) this->_car.edir = Car::EDirection::DOWN_RIGHT;

Quiero evitar la cadena if s; Es realmente feo. ¿Hay otra forma, posiblemente más limpia, de escribir esto?


Cree una matriz, cada elemento del cual está asociado con un bloque de 30 grados:

Car::EDirection dirlist[] = { Car::EDirection::RIGHT, Car::EDirection::UP_RIGHT, Car::EDirection::UP, Car::EDirection::UP, Car::EDirection::UP_LEFT, Car::EDirection::LEFT, Car::EDirection::LEFT, Car::EDirection::DOWN_LEFT, Car::EDirection::DOWN, Car::EDirection::DOWN, Car::EDirection::DOWN_RIGHT, Car::EDirection::RIGHT };

Luego puede indexar la matriz con el ángulo / 30:

this->_car.edir = dirlist[(this->_car.getAbsoluteAngle() % 360) / 30];

No se requieren comparaciones ni ramificaciones.

Sin embargo, el resultado está ligeramente alejado del original. Los valores en los bordes, es decir, 30, 60, 120, etc. se colocan en la siguiente categoría. Por ejemplo, en el código original, los valores válidos para UP_RIGHT son de 31 a 60. El código anterior asigna de 30 a 59 a UP_RIGHT .

Podemos evitar esto restando 1 del ángulo:

this->_car.edir = dirlist[((this->_car.getAbsoluteAngle() - 1) % 360) / 30];

Esto ahora nos da RIGHT por 30, UP_RIGHT por 60, etc.

En el caso de 0, la expresión se convierte en (-1 % 360) / 30 . Esto es válido porque -1 % 360 == -1 y -1 / 30 == 0 , por lo que todavía obtenemos un índice de 0.

La sección 5.6 del estándar C ++ confirma este comportamiento:

4 El operador / binario produce el cociente, y el operador % binario produce el resto de la división de la primera expresión por la segunda. Si el segundo operando de / o % es cero, el comportamiento es indefinido. Para operandos integrales, el operador / produce el cociente algebraico con cualquier parte fraccional descartada. si el cociente a/b es representable en el tipo del resultado, (a/b)*b + a%b es igual a a .

EDITAR:

Se plantearon muchas preguntas con respecto a la legibilidad y mantenibilidad de una construcción como esta. La respuesta dada por motoDrizzt es un buen ejemplo de simplificación de la construcción original que es más fácil de mantener y no es tan "fea".

Ampliando su respuesta, aquí hay otro ejemplo haciendo uso del operador ternario. Dado que cada caso en la publicación original se asigna a la misma variable, el uso de este operador puede ayudar a aumentar aún más la legibilidad.

int angle = ((this->_car.getAbsoluteAngle() % 360) + 360) % 360; this->_car.edir = (angle <= 30) ? Car::EDirection::RIGHT : (angle <= 60) ? Car::EDirection::UP_RIGHT : (angle <= 120) ? Car::EDirection::UP : (angle <= 150) ? Car::EDirection::UP_LEFT : (angle <= 210) ? Car::EDirection::LEFT : (angle <= 240) ? Car::EDirection::DOWN_LEFT : (angle <= 300) ? Car::EDirection::DOWN: (angle <= 330) ? Car::EDirection::DOWN_RIGHT : Car::EDirection::RIGHT;


En pseudocódigo:

angle = (angle + 30) %360; // Offset by 30.

Entonces, tenemos 0-60 , 60-90 , 90-150 , ... como categorías. En cada cuadrante con 90 grados, una parte tiene 60, una parte tiene 30. Entonces, ahora:

i = angle / 90; // Figure out the quadrant. Could be 0, 1, 2, 3 j = (angle - 90 * i) >= 60? 1: 0; // In the quardrant is it perfect (eg: RIGHT) or imperfect (eg: UP_RIGHT)? index = i * 2 + j;

Use el índice en una matriz que contenga las enumeraciones en el orden apropiado.


Ese código no es feo, es simple, práctico, legible y fácil de entender. Se aislará en su propio método, por lo que nadie tendrá que lidiar con eso en la vida cotidiana. Y en caso de que alguien tenga que verificarlo, tal vez porque está depurando su aplicación para un problema en otro lugar, es tan fácil que le tomará dos segundos entender el código y lo que hace.

Si estuviera haciendo tal depuración, estaría feliz de no tener que pasar cinco minutos tratando de entender qué hace su función. En este sentido, todas las demás funciones fallan por completo, ya que cambian una rutina simple, olvidada, libre de errores, en un lío complicado que las personas al depurar se verán obligadas a analizar y probar profundamente. Como gerente de proyectos, me enfurecería mucho que un desarrollador tomara una tarea simple y, en lugar de implementarlo de una manera simple e inofensiva, pierda el tiempo para implementarlo de una manera demasiado complicada. Solo piense todo el tiempo que desperdició pensando en ello, luego diríjase a SO preguntando, y todo por el simple hecho de empeorar el mantenimiento y la legibilidad de la cosa.

Dicho esto, hay un error común en su código que lo hace menos legible, y un par de mejoras que puede hacer con bastante facilidad:

int angle = this->_car.getAbsoluteAngle(); if (angle <= 30 || angle >= 330) return Car::EDirection::RIGHT; else if (angle <= 60) return Car::EDirection::UP_RIGHT; else if (angle <= 120) return Car::EDirection::UP; else if (angle <= 150) return Car::EDirection::UP_LEFT; else if (angle <= 210) return Car::EDirection::LEFT; else if (angle <= 240) return Car::EDirection::DOWN_LEFT; else if (angle <= 300) return Car::EDirection::DOWN; else if (angle <= 330) return Car::EDirection::DOWN_RIGHT;

Ponga esto en un método, asigne el valor devuelto al objeto, contraiga el método y olvídelo por el resto de la eternidad.

PD: hay otro error por encima del umbral de 330, pero no sé cómo quieres tratarlo, así que no lo solucioné en absoluto.

Actualización posterior

Según el comentario, incluso puede deshacerse de lo demás si lo hace:

int angle = this->_car.getAbsoluteAngle(); if (angle <= 30 || angle >= 330) return Car::EDirection::RIGHT; if (angle <= 60) return Car::EDirection::UP_RIGHT; if (angle <= 120) return Car::EDirection::UP; if (angle <= 150) return Car::EDirection::UP_LEFT; if (angle <= 210) return Car::EDirection::LEFT; if (angle <= 240) return Car::EDirection::DOWN_LEFT; if (angle <= 300) return Car::EDirection::DOWN; if (angle <= 330) return Car::EDirection::DOWN_RIGHT;

No lo hice porque siento que cierto punto se convierte solo en una cuestión de preferencias propias, y el alcance de mi respuesta fue (y es) darle una perspectiva diferente a su preocupación por la "fealdad del código". De todos modos, como dije, alguien lo señaló en los comentarios y creo que tiene sentido mostrarlo.


Ignorando su primer if es un caso especial, los restantes siguen exactamente el mismo patrón: un mínimo, máximo y dirección; pseudocódigo:

if (angle > min && angle <= max) _car.edir = direction;

Hacer este verdadero C ++ podría verse así:

enum class EDirection { NONE, RIGHT, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT, DOWN, DOWN_RIGHT }; struct AngleRange { int min, max; EDirection direction; };

Ahora, en lugar de escribir un montón de if s, solo recorra sus diversas posibilidades:

EDirection direction_from_angle(int angle, const std::vector<AngleRange>& angleRanges) { for (auto&& angleRange : angleRanges) { if ((angle > angleRange.min) && (angle <= angleRange.max)) return angleRange.direction; } return EDirection::NONE; }

( throw una excepción en lugar de return NONE es otra opción).

A lo que luego llamarías:

_car.edir = direction_from_angle(_car.getAbsoluteAngle(), { {30, 60, EDirection::UP_RIGHT}, {60, 120, EDirection::UP}, // ... etc. });

Esta técnica se conoce como programación basada en datos . Además de deshacerse de un montón de ifs, le permitiría usar fácilmente agregar más instrucciones (por ejemplo, NNW) o reducir el número (izquierda, derecha, arriba, abajo) sin volver a trabajar otro código.

(Manejar su primer caso especial se deja como "un ejercicio para el lector" :-))


Llego tarde a la fiesta, pero podríamos usar banderas de enumeración y controles de rango para hacer algo ordenado.

enum EDirection { RIGHT = 0x01, LEFT = 0x02, UP = 0x04, DOWN = 0x08, DOWN_RIGHT = DOWN | RIGHT, DOWN_LEFT = DOWN | LEFT, UP_RIGHT = UP | RIGHT, UP_LEFT = UP | LEFT, // just so we be clear, these won''t have much use though IMPOSSIBLE_H = RIGHT | LEFT, IMPOSSIBLE_V = UP | DOWN };

la comprobación (pseudocódigo), suponiendo que el ángulo es absoluto (entre 0 y 360):

int up = (angle > 30 && angle < 150) * EDirection.UP; int down = (angle > 210 && angle < 330) * EDirection.DOWN; int right = (angle <= 60 || angle >= 330) * EDirection.Right; int left = (angle >= 120 && angle <= 240) * EDirection.LEFT; EDirection direction = (Direction)(up | down | right | left); switch(direction){ case RIGHT: // do right break; case UP_RIGHT: // be honest break; case UP: // whats up break; case UP_LEFT: // do you even left break; case LEFT: // 5 done 3 to go break; case DOWN_LEFT: // your''re driving me to a corner here break; case DOWN: // :( break; case DOWN_RIGHT: // completion break; // hey, we mustn''t let these slide case IMPOSSIBLE_H: case IMPOSSIBLE_V: // treat your impossible case here! break; }


Puede usar map::lower_bound y almacenar el límite superior de cada ángulo en un mapa.

Ejemplo de trabajo a continuación:

#include <cassert> #include <map> enum Direction { RIGHT, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT, DOWN, DOWN_RIGHT }; using AngleDirMap = std::map<int, Direction>; AngleDirMap map = { { 30, RIGHT }, { 60, UP_RIGHT }, { 120, UP }, { 150, UP_LEFT }, { 210, LEFT }, { 240, DOWN_LEFT }, { 300, DOWN }, { 330, DOWN_RIGHT }, { 360, RIGHT } }; Direction direction(int angle) { assert(angle >= 0 && angle <= 360); auto it = map.lower_bound(angle); return it->second; } int main() { Direction d; d = direction(45); assert(d == UP_RIGHT); d = direction(30); assert(d == RIGHT); d = direction(360); assert(d == RIGHT); return 0; }


Si realmente desea evitar la duplicación, puede expresar esto como una fórmula matemática.

En primer lugar, suponga que estamos usando la enumeración de @ Geek

Enum EDirection { RIGHT =0, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT,DOWN, DOWN_RIGHT}

Ahora podemos calcular la enumeración usando matemáticas enteras (sin la necesidad de matrices).

EDirection angle2dir(int angle) { int d = ( ((angle%360)+360)%360-1)/30; d-=d/3; //some directions cover a 60 degree arc d%=8; //printf ("assert(angle2dir(%3d)==%-10s);/n",angle,dir2str[d]); return (EDirection) d; }

Como señala @motoDrizzt, el código conciso no es necesariamente un código legible. Tiene la pequeña ventaja de que expresarlo como matemática hace explícito que algunas direcciones cubren un arco más amplio. Si desea ir en esta dirección, puede agregar algunas afirmaciones para ayudar a comprender el código.

assert(angle2dir( 0)==RIGHT ); assert(angle2dir( 30)==RIGHT ); assert(angle2dir( 31)==UP_RIGHT ); assert(angle2dir( 60)==UP_RIGHT ); assert(angle2dir( 61)==UP ); assert(angle2dir(120)==UP ); assert(angle2dir(121)==UP_LEFT ); assert(angle2dir(150)==UP_LEFT ); assert(angle2dir(151)==LEFT ); assert(angle2dir(210)==LEFT ); assert(angle2dir(211)==DOWN_LEFT ); assert(angle2dir(240)==DOWN_LEFT ); assert(angle2dir(241)==DOWN ); assert(angle2dir(300)==DOWN ); assert(angle2dir(301)==DOWN_RIGHT); assert(angle2dir(330)==DOWN_RIGHT); assert(angle2dir(331)==RIGHT ); assert(angle2dir(360)==RIGHT );

Después de agregar las afirmaciones, ha agregado la duplicación, pero la duplicación en las afirmaciones no es tan mala. Si tiene una afirmación inconsistente, lo descubrirá pronto. Las afirmaciones se pueden compilar fuera de la versión de lanzamiento para no hinchar el ejecutable que distribuye. Sin embargo, este enfoque es probablemente el más aplicable si desea optimizar el código en lugar de hacerlo menos feo.


#include <iostream> enum Direction { UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT, LEFT, UP_LEFT }; Direction GetDirectionForAngle(int angle) { const Direction slices[] = { RIGHT, UP_RIGHT, UP, UP, UP_LEFT, LEFT, LEFT, DOWN_LEFT, DOWN, DOWN, DOWN_RIGHT, RIGHT }; return slices[(((angle % 360) + 360) % 360) / 30]; } int main() { // This is just a test case that covers all the possible directions for (int i = 15; i < 360; i += 30) std::cout << GetDirectionForAngle(i) << '' ''; return 0; }

Así es como lo haría. (Según mi comentario anterior).


switch (this->_car.getAbsoluteAngle() / 30) // integer division { case 0: case 11: this->_car.edir = Car::EDirection::RIGHT; break; case 1: this->_car.edir = Car::EDirection::UP_RIGHT; break; ... case 10: this->_car.edir = Car::EDirection::DOWN_RIGHT; break; }