Wie vermeide ich, wenn / sonst wenn Kette, wenn eine Überschrift in 8 Richtungen klassifiziert wird?

111

Ich habe folgenden Code:

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;

Ich möchte die ifKette vermeiden . es ist wirklich hässlich. Gibt es eine andere, möglicherweise sauberere Art, dies zu schreiben?

Oraekia
quelle
77
@Oraekia Es würde viel weniger hässlich aussehen, weniger zu tippen und besser zu lesen, wenn Sie das this->_car.getAbsoluteAngle()einmal vor der ganzen Kaskade ficken .
πάντα ῥεῖ
26
All diese explizite Dereferenzierung von this( this->) ist nicht erforderlich und trägt nicht wirklich zur Lesbarkeit bei.
Jesper Juhl
2
@Neil Paar als Schlüssel, Aufzählung als Wert, benutzerdefinierte Suche Lambda.
πάντα ῥεῖ
56
Ohne all diese >Tests wäre der Code viel weniger hässlich . Sie werden nicht benötigt, da jeder von ihnen bereits in der vorherigen ifAnweisung (in entgegengesetzter Richtung) getestet wurde .
Pete Becker
10
@PeteBecker Das ist einer meiner Lieblingsbeschwerden über Code wie diesen. Zu viele Programmierer verstehen das nicht else if.
Barmar

Antworten:

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

So würde ich es machen. (Gemäß meinem vorherigen Kommentar).

Borgleader
quelle
92
Ich dachte buchstäblich, ich hätte dort für eine Sekunde den Konami-Code anstelle der Aufzählung gesehen.
Zano
21
@CodesInChaos: C99 und C ++ haben die gleichen Anforderungen wie C #: dass wenn q = a/bund r = a%bdann q * b + rgleich sein müssen a. Daher ist es in C99 legal, dass ein Rest negativ ist. BorgLeader, mit dem Sie das Problem beheben können (((angle % 360) + 360) % 360) / 30.
Eric Lippert
7
@ericlippert, Sie und Ihre Kenntnisse in Computermathematik beeindrucken weiterhin.
Gregsdennis
33
Dies ist sehr klug, aber völlig unlesbar und wahrscheinlich nicht mehr wartbar. Daher bin ich nicht der Meinung, dass es eine gute Lösung für die wahrgenommene "Hässlichkeit" des Originals ist. Ich denke, hier gibt es ein Element des persönlichen Geschmacks, aber ich finde die aufgeräumten Verzweigungsversionen von x4u und motoDrizzt sehr vorzuziehen.
IMSoP
4
@cyanbeam die for-Schleife in main ist nur eine "Demo", GetDirectionForAnglewas ich als Ersatz für die if / else-Kaskade vorschlage, sie sind beide O (1) ...
Borgleader
71

Sie können map::lower_bounddie Obergrenze jedes Winkels in einer Karte verwenden und speichern.

Arbeitsbeispiel unten:

#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;
}
Steve Lorimer
quelle
Keine Aufteilung erforderlich. Gut!
O. Jones
17
@ O.Jones: Die Division durch eine Kompilierungszeitkonstante ist ziemlich billig, nur eine Multiplikation und einige Verschiebungen. Ich würde mit einer der table[angle%360/30]Antworten gehen, weil es billig und verzweigungslos ist. Weitaus billiger als eine Baumsuchschleife, wenn diese zu asm kompiliert wird, das der Quelle ähnlich ist. ( std::unordered_mapist normalerweise eine Hash-Tabelle, aber std::mapnormalerweise ein rot-schwarzer Binärbaum. Die akzeptierte Antwort wird effektiv angle%360 / 30als perfekte Hash-Funktion für Winkel verwendet (nach dem Replizieren einiger Einträge, und Bijays Antwort vermeidet dies sogar mit einem Offset)).
Peter Cordes
2
Sie können lower_boundauf einem sortierten Array verwenden. Das wäre viel effizienter als ein map.
Wilx
Die @ PeterCordes-Kartensuche ist einfach zu schreiben und zu warten. Wenn sich Bereiche ändern, kann das Aktualisieren des Hashing-Codes zu Fehlern führen. Wenn Bereiche ungleichmäßig werden, kann dies einfach auseinanderfallen. Wenn dieser Code nicht leistungskritisch ist, würde ich mich nicht darum kümmern.
OhJeez
@OhJeez: Sie sind bereits ungleichmäßig, was durch den gleichen Wert in mehreren Buckets behandelt wird. Verwenden Sie einfach einen kleineren Teiler, um mehr Eimer zu erhalten, es sei denn, dies bedeutet, dass Sie einen sehr kleinen Teiler verwenden und viel zu viele Eimer haben. Wenn die Leistung keine Rolle spielt, ist eine if / else-Kette auch nicht schlecht, wenn sie durch Ausklammern this->_car.getAbsoluteAngle()mit einer tmp-Variable und Entfernen des redundanten Vergleichs aus den einzelnen OP- if()Klauseln vereinfacht wird (Überprüfung auf etwas, das bereits übereinstimmt das vorhergehende if ()). Oder verwenden Sie den Vorschlag von @ wilx für sortierte Arrays.
Peter Cordes
58

Erstellen Sie ein Array, dessen jedes Element einem Block von 30 Grad zugeordnet ist:

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

Dann können Sie das Array mit dem Winkel / 30 indizieren:

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

Keine Vergleiche oder Verzweigungen erforderlich.

Das Ergebnis weicht jedoch geringfügig vom Original ab. Werte an den Rändern, dh 30, 60, 120 usw., werden in die nächste Kategorie eingeordnet. Im ursprünglichen Code sind die gültigen Werte für UP_RIGHTbeispielsweise 31 bis 60. Der obige Code weist 30 bis 59 zu UP_RIGHT.

Wir können dies umgehen, indem wir 1 vom Winkel abziehen:

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

Dies gibt uns jetzt RIGHTfür 30, UP_RIGHTfür 60 usw.

Im Fall von 0 wird der Ausdruck (-1 % 360) / 30. Dies ist gültig, weil -1 % 360 == -1und -1 / 30 == 0, so dass wir immer noch einen Index von 0 erhalten.

Abschnitt 5.6 des C ++ - Standards bestätigt dieses Verhalten:

4 Der binäre /Operator liefert den Quotienten und der binäre %Operator den Rest aus der Division des ersten Ausdrucks durch den zweiten. Wenn der zweite Operand von /oder %Null ist, ist das Verhalten undefiniert. Für integrale Operanden /liefert der Operator den algebraischen Quotienten, wobei jeder gebrochene Teil verworfen wird. Wenn der Quotient a/bin der Art des Ergebnisses darstellbar ist, (a/b)*b + a%bist gleich a.

BEARBEITEN:

Es wurden viele Fragen zur Lesbarkeit und Wartbarkeit eines solchen Konstrukts aufgeworfen. Die Antwort von motoDrizzt ist ein gutes Beispiel für die Vereinfachung des ursprünglichen Konstrukts, das wartbarer und nicht ganz so "hässlich" ist.

Um seine Antwort zu erweitern, hier ein weiteres Beispiel, das den ternären Operator verwendet. Da jeder Fall im ursprünglichen Beitrag derselben Variablen zugewiesen wird, kann die Verwendung dieses Operators dazu beitragen, die Lesbarkeit weiter zu verbessern.

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;
dbush
quelle
49

Dieser Code ist nicht hässlich, er ist einfach, praktisch, lesbar und leicht zu verstehen. Es wird auf seine eigene Weise isoliert, so dass sich niemand im Alltag damit auseinandersetzen muss. Und nur für den Fall, dass jemand dies überprüfen muss - möglicherweise, weil er Ihre Anwendung für ein Problem an einem anderen Ort debuggt -, ist es so einfach, dass er zwei Sekunden benötigt, um den Code und seine Funktionsweise zu verstehen.

Wenn ich ein solches Debugging durchführen würde, wäre ich froh, nicht fünf Minuten damit verbringen zu müssen, zu verstehen, was Ihre Funktion tut. In dieser Hinsicht scheitern alle anderen Funktionen vollständig, da sie eine einfache, fehlerfreie Routine zum Vergessen und Ändern in einem komplizierten Durcheinander ändern, das die Benutzer beim Debuggen gezwungen sind, gründlich zu analysieren und zu testen. Als Projektmanager würde ich mich sehr darüber aufregen, dass ein Entwickler eine einfache Aufgabe übernimmt und anstatt sie auf einfache, harmlose Weise zu implementieren, Zeit verschwendet, um sie auf eine zu komplizierte Weise zu implementieren. Denken Sie nur die ganze Zeit nach, die Sie damit verschwendet haben, darüber nachzudenken und dann zu SO zu fragen, und das alles nur, um die Wartung und Lesbarkeit der Sache zu verschlechtern.

Das heißt, es gibt einen häufigen Fehler in Ihrem Code, der ihn weniger lesbar macht, und ein paar Verbesserungen, die Sie ganz einfach vornehmen können:

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;

Fügen Sie dies in eine Methode ein, weisen Sie dem Objekt den zurückgegebenen Wert zu, reduzieren Sie die Methode und vergessen Sie ihn für den Rest der Ewigkeit.

PS: Es gibt einen weiteren Fehler über der Schwelle von 330, aber ich weiß nicht, wie Sie ihn behandeln möchten, deshalb habe ich ihn überhaupt nicht behoben.


Späteres Update

Laut Kommentar können Sie sogar das andere loswerden, wenn überhaupt:

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;

Ich habe es nicht getan, weil ich das Gefühl habe, dass ein bestimmter Punkt nur eine Frage der eigenen Vorlieben ist, und der Umfang meiner Antwort war (und ist), Ihrer Besorgnis über "Hässlichkeit des Codes" eine andere Perspektive zu geben. Wie gesagt, jemand hat in den Kommentaren darauf hingewiesen, und ich denke, es ist sinnvoll, es zu zeigen.

motoDrizzt
quelle
1
Was zum Teufel sollte das bewirken?
Abstraktion ist alles.
8
Wenn Sie diesen Weg gehen wollen, sollten Sie zumindest das Unnötige loswerden else if, ifist ausreichend.
Am
10
@ Ich bin völlig anderer Meinung über die else if. Ich finde es nützlich, einen Blick auf einen Codeblock werfen zu können und festzustellen, dass es sich eher um einen Entscheidungsbaum als um eine Gruppe nicht zusammenhängender Anweisungen handelt. Ja, elseoder breaksind für den Compiler nach a nicht erforderlich return, aber sie sind nützlich für den Menschen, der einen Blick auf den Code wirft.
IMSoP
@ Ðаn Ich habe noch nie eine Sprache gesehen, in der dort eine zusätzliche Verschachtelung erforderlich wäre. Entweder gibt es ein separates elseif/ elsifSchlüsselwort, oder Sie verwenden technisch gesehen einen Block mit einer Anweisung, der zufällig beginnt if, genau wie hier. Ein kurzes Beispiel dafür, woran Sie denken und woran ich denke: gist.github.com/IMSoP/90bc1e9e2c56d8314413d7347e76532a
IMSoP
7
@ Yesаn Ja, ich stimme zu, das wäre schrecklich. Aber es ist nicht das, elsewas Sie dazu bringen, es ist ein schlechter Styleguide, der nicht else ifals eindeutige Aussage erkannt wird . Ich würde immer geschweifte Klammern verwenden, aber ich würde niemals solchen Code schreiben, wie ich in meinem Kern gezeigt habe.
IMSoP
39

Im Pseudocode:

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

So haben wir 0-60, 60-90, 90-150, ... als die Kategorien. In jedem Quadranten mit 90 Grad hat ein Teil 60, ein Teil 30. Also, jetzt:

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;

Verwenden Sie den Index in einem Array, das die Aufzählungen in der entsprechenden Reihenfolge enthält.

Bijay Gurung
quelle
7
Das ist gut, wahrscheinlich die beste Antwort hier. Wenn der ursprüngliche Fragesteller später seine Verwendung der Aufzählung betrachtete, stellte er wahrscheinlich fest, dass er einen Fall hat, in dem sie nur wieder in eine Zahl umgewandelt wird! Das vollständige Eliminieren der Aufzählung und das Festhalten an einer Richtungs-Ganzzahl ist wahrscheinlich an anderen Stellen in seinem Code sinnvoll, und diese Antwort bringt Sie direkt dorthin.
Bill K
18
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;
}
Caleth
quelle
angle = this -> _ car.getAbsoluteAngle (); Sektor = (Winkel% 360) / 30; Ergebnis sind 12 Sektoren. Indizieren Sie dann in ein Array oder verwenden Sie switch / case wie oben - der Compiler verwandelt sich trotzdem in eine Sprungtabelle.
ChuckCottrill
1
Schalten Sie nicht wirklich besser als wenn / sonst Ketten.
Bill K
5
@ BillK: Es könnte sein, dass der Compiler daraus eine Tabellensuche für Sie macht. Das ist wahrscheinlicher als bei einer if / else-Kette. Da es jedoch einfach ist und keine architekturspezifischen Tricks erfordert, ist es wahrscheinlich am besten, die Tabellensuche in den Quellcode zu schreiben.
Peter Cordes
Im Allgemeinen sollte die Leistung kein Problem darstellen - es ist Lesbarkeit und Wartbarkeit - jeder Schalter und jede if / else-Kette bedeutet normalerweise eine Menge unordentlicher Code zum Kopieren und Einfügen, der an mehreren Stellen aktualisiert werden muss, wenn Sie ein neues Element hinzufügen. Vermeiden Sie am besten beides und versuchen Sie, Tabellen, Berechnungen oder Daten aus einer Datei zu versenden und als Daten zu behandeln, wenn Sie können.
Bill K
PeterCordes, der Compiler, generiert wahrscheinlich identischen Code für die LUT wie für den Switch. @ BillK Sie können den Schalter in eine 0..12 -> Car :: EDirection-Funktion extrahieren, die eine LUT
Caleth
16

Wenn Sie Ihren ersten ignorieren, ifwas ein Sonderfall ist, folgen die übrigen genau dem gleichen Muster: a min, max und Richtung; Pseudocode:

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

Das Erstellen dieses echten C ++ könnte folgendermaßen aussehen:

enum class EDirection {  NONE,
   RIGHT, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT, DOWN, DOWN_RIGHT };

struct AngleRange
{
    int min, max;
    EDirection direction;
};

Anstatt ein paar ifs zu schreiben, sollten Sie jetzt einfach Ihre verschiedenen Möglichkeiten durchgehen:

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

( throwIng eine Ausnahme und nicht returning NONEist eine weitere Option).

Was Sie dann nennen würden:

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

Diese Technik ist als datengesteuerte Programmierung bekannt . Abgesehen ifdavon, dass Sie ein paar s loswerden , können Sie auf einfache Weise weitere Richtungen hinzufügen (z. B. NNW) oder die Anzahl (links, rechts, oben, unten) reduzieren, ohne anderen Code zu überarbeiten.


(Die Behandlung Ihres ersten Sonderfalls bleibt als "Übung für den Leser" übrig :-))

Nаn
quelle
1
Technisch gesehen könnten Sie min eliminieren, da alle Winkelbereiche übereinstimmen, was die Bedingung if(angle <= angleRange.max)für die Verwendung von C ++ 11-Funktionen wie z enum class. B. auf +1 reduzieren würde .
Pharap
12

Obwohl die vorgeschlagenen Varianten, die auf einer Nachschlagetabelle für angle / 30basieren, wahrscheinlich vorzuziehen sind, gibt es hier eine Alternative, die eine fest codierte binäre Suche verwendet, um die Anzahl der Vergleiche zu minimieren.

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
}
x4u
quelle
2

Wenn Sie Doppelarbeit wirklich vermeiden möchten, können Sie dies als mathematische Formel ausdrücken.

Nehmen wir zunächst an, dass wir @ Geek's Enum verwenden

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

Jetzt können wir die Aufzählung mit ganzzahliger Mathematik berechnen (ohne dass Arrays erforderlich sind).

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

Wie @motoDrizzt hervorhebt, ist prägnanter Code nicht unbedingt lesbarer Code. Es hat den kleinen Vorteil, dass durch das Ausdrücken als Mathematik deutlich wird, dass einige Richtungen einen breiteren Bogen abdecken. Wenn Sie in diese Richtung gehen möchten, können Sie einige Asserts hinzufügen, um den Code besser zu verstehen.

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

Nachdem Sie die Asserts hinzugefügt haben, haben Sie eine Duplizierung hinzugefügt, aber die Duplizierung in Asserts ist nicht so schlimm. Wenn Sie eine inkonsistente Behauptung haben, werden Sie es früh genug herausfinden. Asserts können aus der Release-Version kompiliert werden, um die von Ihnen verteilte ausführbare Datei nicht aufzublähen. Dennoch ist dieser Ansatz wahrscheinlich am besten geeignet, wenn Sie den Code optimieren möchten, anstatt ihn nur weniger hässlich zu machen.

gmatht
quelle
1

Ich bin zu spät zur Party, aber wir könnten Enum-Flags und Range Checks verwenden, um etwas Ordentliches zu tun.

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

die Überprüfung (Pseudocode) unter der Annahme, dass der Winkel absolut ist (zwischen 0 und 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;
}
Leonardo Pina
quelle