Jak uniknąć łańcucha if / else if podczas klasyfikowania nagłówka w 8 kierunkach?

111

Mam następujący kod:

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;

Chcę uniknąć ifłańcucha; to naprawdę brzydkie. Czy istnieje inny, być może bardziej przejrzysty sposób napisania tego?

Oraekia
źródło
77
@Oraekia Wyglądałoby to o wiele mniej brzydko, mniej do pisania i lepiej się czyta, gdybyś fectchował this->_car.getAbsoluteAngle()raz przed całą kaskadą.
πάντα ῥεῖ
26
Cała ta jawna dereferencja this( this->) nie jest potrzebna i tak naprawdę nie wpływa na czytelność ..
Jesper Juhl
2
@Neil Para jako klucz, wyliczenie jako wartość, niestandardowa lambda wyszukiwania.
πάντα ῥεῖ
56
Kod byłby dużo mniej brzydki bez tych wszystkich >testów; nie są potrzebne, ponieważ każdy z nich został już przetestowany (w przeciwnym kierunku) w poprzednim ifoświadczeniu.
Pete Becker
10
@PeteBecker To jedno z moich ulubionych irytujących treści związanych z takim kodem. Zbyt wielu programistów nie rozumie else if.
Barmar

Odpowiedzi:

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

Tak bym to zrobił. (Jak na mój poprzedni komentarz).

Borgleader
źródło
92
Dosłownie myślałem, że przez sekundę widziałem kod Konami zamiast wyliczenia.
Zano
21
@CodesInChaos: C99 i C ++ mają te same wymagania co C #: jeśli q = a/bi r = a%bwtedy q * b + rmuszą być równe a. Tak więc w C99 legalne jest, aby reszta była ujemna. BorgLeader, możesz rozwiązać problem za pomocą (((angle % 360) + 360) % 360) / 30.
Eric Lippert
7
@ericlippert, Ty i Twoja wiedza z zakresu matematyki obliczeniowej nadal imponujecie.
gregsdennis
33
Jest to bardzo sprytne, ale całkowicie nieczytelne i mało prawdopodobne, że da się je naprawić, więc nie zgadzam się, że jest to dobre rozwiązanie postrzeganej „brzydoty” oryginału. Wydaje mi się, że jest tu element osobistego gustu, ale uważam, że wyczyszczone wersje rozgałęzień stworzone przez x4u i motoDrizzt są zdecydowanie lepsze.
IMSoP
4
@cyanbeam pętla for w main to tylko „demo”, GetDirectionForAngleproponuję jako zamiennik dla kaskady if / else, obie są O (1) ...
Borgleader
71

Możesz użyć map::lower_boundi zapisać górną granicę każdego kąta na mapie.

Przykład roboczy poniżej:

#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
źródło
Nie jest wymagany podział. Dobry!
O. Jones
17
@ O.Jones: Dzielenie przez stałą czasu kompilacji jest dość tanie, wystarczy pomnożyć i kilka przesunięć. Poszedłbym z jedną z table[angle%360/30]odpowiedzi, ponieważ jest tani i bez gałęzi. Znacznie tańsze niż pętla przeszukiwania drzewa, jeśli kompiluje się do asm, który jest podobny do źródła. ( std::unordered_mapjest to zwykle tablica mieszająca, ale std::mapzazwyczaj jest to czerwono-czarne drzewo binarne. Zaakceptowana odpowiedź skutecznie wykorzystuje angle%360 / 30jako doskonałą funkcję mieszającą dla kątów (po replikacji kilku wpisów, a odpowiedź Bijaya unika tego nawet z przesunięciem)).
Peter Cordes
2
Możesz użyć lower_boundna posortowanej tablicy. Byłoby to o wiele bardziej wydajne niż map.
wilx
Wyszukiwanie map @PeterCordes jest łatwe do napisania i łatwe w utrzymaniu. Jeśli zmienią się zakresy, aktualizacja kodu haszującego może spowodować błędy, a jeśli zakresy staną się niejednorodne, może po prostu się rozpaść. O ile ten kod nie jest krytyczny dla wydajności, nie zawracałbym sobie głowy.
OhJeez
@OhJeez: Już są niejednolite, co jest obsługiwane przez posiadanie tej samej wartości w wielu zasobnikach. Po prostu użyj mniejszego dzielnika, aby uzyskać więcej segmentów, chyba że oznacza to użycie bardzo małego dzielnika i posiadanie zbyt wielu segmentów. Ponadto, jeśli wydajność nie ma znaczenia, to łańcuch if / else również nie jest zły, jeśli zostanie uproszczony poprzez uwzględnienie this->_car.getAbsoluteAngle()zmiennej tmp i usunięcie zbędnego porównania z każdej z if()klauzul OP (sprawdzenie, czy coś już pasowałoby poprzedni if ​​()). Lub użyj sugestii posortowanej tablicy @ wilx.
Peter Cordes
58

Utwórz tablicę, której każdy element jest powiązany z blokiem o 30 stopniach:

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

Następnie możesz indeksować tablicę z kątem / 30:

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

Nie są wymagane żadne porównania ani rozgałęzienia.

Wynik jest jednak nieco odbiegający od oryginału. Wartości na granicach, tj. 30, 60, 120 itd. Są umieszczane w kolejnej kategorii. Na przykład w oryginalnym kodzie prawidłowe wartości UP_RIGHTto 31 do 60. Powyższy kod przypisuje 30 do 59 do UP_RIGHT.

Możemy to obejść, odejmując 1 od kąta:

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

To daje nam teraz RIGHTza 30, UP_RIGHTza 60 itd.

W przypadku 0 wyrażenie staje się (-1 % 360) / 30. Jest to ważne, ponieważ -1 % 360 == -1i -1 / 30 == 0, więc nadal otrzymujemy indeks 0.

Sekcja 5.6 standardu C ++ potwierdza to zachowanie:

4/ Operator binarny daje iloraz, a %operator binarny daje resztę z dzielenia pierwszego wyrażenia przez drugie. Jeśli drugi argument operacji /lub %wynosi zero, zachowanie jest niezdefiniowane. Dla argumentów całkowitych /operator daje iloraz algebraiczny z odrzuconą dowolną częścią ułamkową. jeśli iloraz a/bjest reprezentowalny w typie wyniku, (a/b)*b + a%bjest równy a.

EDYTOWAĆ:

Pojawiło się wiele pytań dotyczących czytelności i łatwości utrzymania takiej konstrukcji. Odpowiedź udzielona przez motoDrizzt jest dobrym przykładem uproszczenia oryginalnej konstrukcji, która jest łatwiejsza w utrzymaniu i nie jest tak „brzydka”.

Rozszerzając jego odpowiedź, oto kolejny przykład wykorzystujący operator trójskładnikowy. Ponieważ każdy przypadek w oryginalnym poście jest przypisywany do tej samej zmiennej, użycie tego operatora może jeszcze bardziej zwiększyć czytelność.

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
źródło
49

Ten kod nie jest brzydki, jest prosty, praktyczny, czytelny i łatwy do zrozumienia. Zostanie wyodrębniony własną metodą, więc na co dzień nikt nie będzie musiał się nim zajmować. I na wypadek, gdyby ktoś musiał to sprawdzić - może dlatego, że debuguje Twoją aplikację pod kątem problemu w innym miejscu - jest to tak łatwe, że zrozumienie kodu i tego, co robi, zajmie mu dwie sekundy.

Gdybym robił taki debug, byłbym szczęśliwy, gdyby nie musiał spędzać pięciu minut na próbach zrozumienia, co robi twoja funkcja. Pod tym względem wszystkie inne funkcje zawodzą całkowicie, ponieważ zmieniają prostą, pozbawioną błędów procedurę, w skomplikowany bałagan, który ludzie podczas debugowania będą zmuszeni do dogłębnej analizy i testowania. Jako kierownik projektu bardzo bym się zdenerwował, gdy programista podjął proste zadanie i zamiast wdrożyć je w prosty, nieszkodliwy sposób, tracę czas na implementację w zbyt skomplikowany sposób. Po prostu pomyśl cały czas, kiedy traciłeś czas na myślenie o tym, a potem przychodziłeś do SO z pytaniem, a wszystko po to, aby pogorszyć konserwację i czytelność rzeczy.

To powiedziawszy, w Twoim kodzie jest typowy błąd, który sprawia, że ​​jest on mniej czytelny, oraz kilka ulepszeń, które możesz zrobić dość łatwo:

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;

Umieść to w metodzie, przypisz zwróconą wartość do obiektu, zwiń metodę i zapomnij o niej na resztę wieczności.

PS jest inny błąd powyżej progu 330, ale nie wiem, jak chcesz go leczyć, więc w ogóle go nie naprawiłem.


Późniejsza aktualizacja

Zgodnie z komentarzem możesz nawet pozbyć się innych, jeśli w ogóle:

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;

Nie zrobiłem tego, bo czuję, że w pewnym momencie staje się to tylko kwestią własnych preferencji, a zakres mojej odpowiedzi polegał (i jest) na nadanie innej perspektywy Twojej trosce o „brzydotę kodu”. W każdym razie, jak powiedziałem, ktoś w komentarzach zwrócił na to uwagę i myślę, że warto to pokazać.

motoDrizzt
źródło
1
Co do diabła miało to osiągnąć?
Abstrakcja jest wszystkim.
8
jeśli chcesz jechać tą trasą, powinieneś przynajmniej pozbyć się zbędnych else if, ifwystarczy.
2017
10
@ Ðаn Zupełnie nie zgadzam się z else if. Uważam, że warto rzucić okiem na blok kodu i zobaczyć, że jest to drzewo decyzyjne, a nie grupa niepowiązanych ze sobą instrukcji. Tak, elselub breaknie są potrzebne kompilatorowi po a return, ale są przydatne dla człowieka, który patrzy na kod.
IMSoP
@ Ðаn Nigdy nie widziałem języka, w którym wymagane byłoby dodatkowe zagnieżdżenie. Albo jest oddzielne słowo kluczowe elseif/ elsif, albo technicznie używasz bloku z jednym wyrażeniem, który tak się zaczyna if, tak jak tutaj. Krótki przykład tego, o czym myślę, i o czym myślę: gist.github.com/IMSoP/90bc1e9e2c56d8314413d7347e76532a
IMSoP
7
@ Ðаn Tak, zgadzam się, że byłoby to okropne. Ale to nie jest elsezmuszanie do tego, jest to kiepski przewodnik po stylu, którego nie można rozpoznać else ifjako wyraźnego stwierdzenia. Zawsze używałbym nawiasów klamrowych, ale nigdy nie pisałbym takiego kodu, jak pokazałem w istocie.
IMSoP
39

W pseudokodzie:

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

Tak, mamy 0-60, 60-90, 90-150, ... jako kategoriach. W każdym kwadrancie o 90 stopniach jedna część ma 60, a jedna 30. Więc teraz:

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;

Użyj indeksu w tablicy zawierającej wyliczenia w odpowiedniej kolejności.

Bijay Gurung
źródło
7
To jest dobra, prawdopodobnie najlepsza odpowiedź tutaj. Jest szansa, że ​​jeśli pierwotny pytający spojrzał na użycie wyliczenia później, stwierdziłby, że ma przypadek, w którym jest on po prostu konwertowany z powrotem na liczbę! Całkowite wyeliminowanie wyliczenia i po prostu trzymanie się liczby całkowitej w kierunku prawdopodobnie ma sens w przypadku innych miejsc w jego kodzie, a ta odpowiedź prowadzi bezpośrednio do tego.
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
źródło
angle = this -> _ car.getAbsoluteAngle (); sektor = (kąt% 360) / 30; Wynik to 12 sektorów. Następnie zindeksuj do tablicy lub użyj przełącznika / przypadku jak powyżej - który kompilator i tak przekształca w tablicę skoków.
ChuckCottrill
1
Przełącznik nie jest lepszy niż łańcuchy if / else.
Bill K
5
@BillK: Może tak być, jeśli kompilator zamieni go na wyszukiwanie w tabeli. Jest to bardziej prawdopodobne niż w przypadku łańcucha if / else. Ale ponieważ jest to łatwe i nie wymaga żadnych sztuczek specyficznych dla architektury, prawdopodobnie najlepiej jest napisać wyszukiwanie tabeli w kodzie źródłowym.
Peter Cordes
Generalnie wydajność nie powinna być problemem - to czytelność i łatwość w utrzymaniu - każdy łańcuch przełączników i jeśli / inaczej zwykle oznacza mnóstwo niechlujnego kodu kopiowania i wklejania, który musi być aktualizowany w wielu miejscach za każdym razem, gdy dodajesz nowy element. Najlepiej unikać obu i wypróbuj tabele, obliczenia lub po prostu załaduj dane z pliku i traktuj je jako dane, jeśli możesz.
Bill K
PeterCordes kompilator prawdopodobnie wygeneruje identyczny kod dla LUT, jak dla przełącznika. @BillK możesz wyodrębnić przełącznik do funkcji 0..12 -> Car :: EDirection, która miałaby równoważne powtórzenie z LUT
Caleth
16

Ignorując pierwszy, ifktóry jest trochę szczególny, pozostałe wszystkie mają ten sam wzór: min, max i kierunek; pseudo kod:

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

Stworzenie tego prawdziwego C ++ może wyglądać następująco:

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

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

Zamiast pisać kilka ifzdań, po prostu przejrzyj różne możliwości:

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

( Inną opcją jest throwraczej wyjątek niż returning NONE).

Które następnie nazwałbyś:

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

Ta technika jest znana jako programowanie oparte na danych . Oprócz pozbycia się kilku ifznaków, pozwoliłoby to na łatwe dodawanie kolejnych kierunków (np. NNW) lub zmniejszanie liczby (w lewo, w prawo, w górę, w dół) bez ponownego przetwarzania innego kodu.


(Zajmowanie się pierwszym specjalnym przypadkiem pozostawiamy jako „ćwiczenie dla czytelnika”. :-))

Ðаn
źródło
1
Technicznie rzecz biorąc, można wyeliminować min, biorąc pod uwagę, że wszystkie zakresy kątów pasują do siebie, co zmniejszyłoby warunek do if(angle <= angleRange.max)ale +1 do korzystania z funkcji C ++ 11, takich jak enum class.
Pharap
12

Chociaż proponowane warianty oparte na tabeli przeglądowej angle / 30są prawdopodobnie preferowane, tutaj jest alternatywa, która wykorzystuje kodowane na stałe wyszukiwanie binarne, aby zminimalizować liczbę porównań.

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
źródło
2

Jeśli naprawdę chcesz uniknąć powielania, możesz to wyrazić wzorem matematycznym.

Przede wszystkim załóżmy, że używamy Enum @ Geek

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

Teraz możemy obliczyć wyliczenie za pomocą matematyki całkowitoliczbowej (bez potrzeby stosowania tablic).

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

Jak wskazuje @motoDrizzt, zwięzły kod niekoniecznie jest kodem czytelnym. Ma tę niewielką zaletę, że wyrażanie tego jako matematyki wyraźnie wskazuje, że niektóre kierunki obejmują szerszy łuk. Jeśli chcesz pójść w tym kierunku, możesz dodać kilka potwierdzeń, które pomogą zrozumieć kod.

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

Po dodaniu potwierdzeń dodałeś duplikację, ale powielanie w potwierdzeniach nie jest takie złe. Jeśli masz niespójne twierdzenie, wkrótce się o tym dowiesz. Potwierdzenia można skompilować poza wydaną wersją, aby nie nadużywać rozpowszechnianego pliku wykonywalnego. Niemniej jednak to podejście jest prawdopodobnie najbardziej odpowiednie, jeśli chcesz zoptymalizować kod, a nie tylko uczynić go mniej brzydkim.

gmatht
źródło
1

Spóźniłem się na imprezę, ale moglibyśmy użyć flag wyliczenia i kontroli zasięgu, aby zrobić coś porządnego.

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

sprawdzanie (pseudokod) przy założeniu, że kąt jest absolutny (między 0 a 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
źródło