111 votes

Comment éviter si / sinon si chaîne lors du classement d'un en-tête dans 8 directions?

J'ai le code suivant:

 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;
 

Je veux éviter la chaîne if s; c'est vraiment moche. Existe-t-il une autre manière, peut-être plus propre, d’écrire cela?

176voto

Borgleader Points 8947
 #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;
}
 

Voici comment je le ferais. (Selon mon commentaire précédent).

71voto

Steve Lorimer Points 2248

Vous pouvez utiliser map::lower_bound et stocker la limite supérieure de chaque angle dans une carte.

Exemple de travail ci-dessous:

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

57voto

dbush Points 8590

Créer un tableau dont chaque élément est associé à un bloc de 30 degrés:

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

Ensuite, vous pouvez l'indice de la matrice avec l'angle / 30:

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

Pas de comparaison ou de branchement nécessaire.

Le résultat est cependant légèrement hors de l'original. Les valeurs sur les frontières, c'est à dire 30, 60, 120, etc. sont placés dans la catégorie suivante. Par exemple, dans le code d'origine les valeurs valides pour UP_RIGHT de 31 à 60. Le code ci-dessus affecte 30 à 59 UP_RIGHT.

On peut contourner ce problème en soustrayant 1 de l'angle:

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

Cela nous offre maintenant RIGHT de 30, UP_RIGHT pour 60, etc.

Dans le cas de 0, l'expression devient (-1 % 360) / 30. Ceci est valable parce qu' -1 % 360 == -1 et -1 / 30 == 0, de sorte que nous obtenons toujours un indice de 0.

L'article 5.6 de la norme C++ confirme ce comportement:

4 Le binaire / opérateur donne le quotient et le binaire % opérateur donne le reste de la division de la première l'expression par le second. Si le second opérande de / ou % est égal à zéro le comportement est indéfini. Intégrale de la opérandes l' /de l'opérateur les rendements de la algébrique quotient avec toute partie fractionnaire jeté. si le quotient a/b est représentable dans le type du résultat, (a/b)*b + a%b est égal à a.

EDIT:

Il y avait beaucoup de questions soulevées au sujet de la lisibilité et la maintenabilité d'une construction de ce genre. La réponse donnée par motoDrizzt est un bon exemple de la simplification de l'original de la construction qui est plus facile à gérer et n'est pas aussi "laid".

L'expansion sur de sa réponse, voici un autre exemple, l'utilisation de l'opérateur ternaire. Puisque chaque cas dans le post original est d'attribuer à la même variable, à l'aide de cet opérateur peut aider à augmenter la lisibilité d'autres.

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;

49voto

motoDrizzt Points 858

Ce code n'est pas laid, c'est simple, pratique, lisible et facile à comprendre. Il sera isolé dans son propre méthode, personne n'aura à traiter avec elle, dans la vie de tous les jours. Et juste au cas où quelqu'un a vérifier, peut-être parce qu'il est le débogage de votre application pour un problème quelque part d'autre - c'est si facile que ça va lui prendre deux secondes pour comprendre le code et ce qu'il fait.

Si je faisais une telle debug, je serais heureux de ne pas avoir à passer cinq minutes à essayer de comprendre ce que votre fonction ne. À cet égard, toutes les autres fonctions qui échouent complètement, car elles peuvent changer d'un simple, oubliez-sur-elle, bugs gratuit de routine, dans un bazar compliqué que les gens lors du débogage seront obligés de profondément analyser et tester. En tant que gestionnaire de projet, moi-même j'avais fortement contrarié par un développeur de prendre une tâche simple, et au lieu de la mettre en œuvre dans un simple, moyen inoffensif, des pertes de temps pour la mettre en œuvre en plus de compliquer façon. Juste penser tout le temps gaspillé à penser à elle, pour ensuite sortir à DONC ce genre de questions, et le tout pour seulement la cause de l'aggravation de l'entretien et de la lisibilité de la chose.

Cela dit, il existe une erreur dans votre code qui font bien moins lisible, et quelques améliorations que vous pouvez le faire assez facilement:

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;

Mettre dans une méthode, attribuer la valeur renvoyée à l'objet, l'effondrement de la méthode, et l'oublier pour le reste de l'éternité.

P. S. il y a un autre bug, plus de 330 seuil, mais je ne sais pas comment vous voulez traiter, donc je n'ai pas le fixer à tous.


Mise à jour ultérieure

Comme chaque commentaire, vous pouvez même vous débarrasser de l'autre si, à tous:

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;

Je ne l'ai pas fait c'est parce que j'ai l'impression que d'un certain point, il devient juste une question de préférences, et la portée de ma réponse était (et est) pour donner un point de vue différent à votre préoccupation quant à la "laideur de code". De toute façon, comme je l'ai dit, quelqu'un l'a fait remarquer dans les commentaires et je pense qu'il est judicieux de le montrer.

39voto

Bijay Gurung Points 1014

En pseudocode:

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

Ainsi, nous avons 0-60 , 60-90 , 90-150 , ... comme catégories. Dans chaque quadrant à 90 degrés, une partie en a 60, une en a 30. Donc, maintenant:

 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;
 

Utilisez l'index dans un tableau contenant les énumérations dans l'ordre approprié.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X