Bagaimana cara menghindari if / else if chain saat mengklasifikasikan heading ke dalam 8 arah?

111

Saya memiliki kode berikut:

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;

Saya ingin menghindari ifrantai s; itu sangat jelek. Adakah cara lain, mungkin lebih bersih, untuk menulis ini?

Oraekia
sumber
77
@Oraekia Ini akan terlihat jauh lebih jelek, kurang untuk mengetik dan lebih baik untuk dibaca jika Anda fectch this->_car.getAbsoluteAngle()sekali sebelum seluruh kaskade.
πάντα ῥεῖ
26
Semua dereferencing eksplisit this( this->) tidak diperlukan dan tidak benar-benar melakukan sesuatu yang baik untuk keterbacaan ..
Jesper Juhl
2
@Neil Pasangkan sebagai kunci, enum sebagai nilai, lambda pencarian kustom.
πάντα ῥεῖ
56
Kode akan menjadi jauh lebih jelek tanpa semua >tes itu; mereka tidak diperlukan, karena masing-masing telah diuji (dalam arah yang berlawanan) dalam ifpernyataan sebelumnya .
Pete Becker
10
@PeteBecker Itu salah satu hewan kesayangan saya tentang kode seperti ini. Terlalu banyak programmer yang tidak mengerti else if.
Barmar

Jawaban:

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

Beginilah cara saya melakukannya. (Sesuai komentar saya sebelumnya).

Borgleader
sumber
92
Saya benar-benar berpikir saya melihat Kode Konami di tempat enum di sana untuk sesaat.
Zano
21
@CodesInChaos: C99 dan C ++ memiliki persyaratan yang sama dengan C #: bahwa jika q = a/bdan r = a%bkemudian q * b + rharus sama a. Jadi sah di C99 untuk sisanya menjadi negatif. BorgLeader, Anda dapat memperbaiki masalah ini dengan (((angle % 360) + 360) % 360) / 30.
Eric Lippert
7
@ericlippert, Anda dan pengetahuan matematika komputasi Anda terus mengesankan.
gregsdennis
33
Ini sangat pintar, tetapi sama sekali tidak dapat dibaca, dan tidak mungkin lebih dapat dipertahankan, jadi saya tidak setuju bahwa ini adalah solusi yang baik untuk "keburukan" aslinya. Ada elemen selera pribadi, di sini, saya rasa, tapi menurut saya versi percabangan yang dibersihkan oleh x4u dan motoDrizzt jauh lebih disukai.
IMSoP
4
@cyanbeam loop for pada main hanyalah sebuah "demo", GetDirectionForAngleadalah apa yang saya usulkan sebagai pengganti kaskade if / else, keduanya adalah O (1) ...
Borgleader
71

Anda dapat menggunakan map::lower_bounddan menyimpan batas atas setiap sudut di peta.

Contoh kerja di bawah ini:

#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
sumber
Tidak ada divisi yang dibutuhkan. Baik!
O. Jones
17
@ O. Jones: Pembagian dengan konstanta waktu kompilasi cukup murah, hanya perkalian dan beberapa pergeseran. Saya akan memilih salah satu table[angle%360/30]jawaban karena murah dan tanpa cabang. Jauh lebih murah daripada loop pencarian pohon, jika ini mengkompilasi ke asm yang mirip dengan sumbernya. ( std::unordered_mapbiasanya tabel hash, tetapi std::mapbiasanya pohon biner merah-hitam. Jawaban yang diterima secara efektif digunakan angle%360 / 30sebagai fungsi hash yang sempurna untuk sudut (setelah mereplikasi beberapa entri, dan jawaban Bijay bahkan menghindarinya dengan offset)).
Peter Cordes
2
Anda dapat menggunakan lower_boundarray yang diurutkan. Itu akan jauh lebih efisien daripada a map.
wilx
Pencarian peta @PeterCordes mudah ditulis dan dipelihara. Jika rentang berubah, memperbarui kode hashing mungkin memperkenalkan bug dan jika rentang menjadi tidak seragam, mungkin akan berantakan. Kecuali kode itu penting untuk kinerja, saya tidak akan repot-repot.
OhJeez
@OhJeez: Mereka sudah tidak seragam, yang ditangani dengan memiliki nilai yang sama di beberapa keranjang. Gunakan saja pembagi yang lebih kecil untuk mendapatkan lebih banyak ember, kecuali jika itu berarti menggunakan pembagi yang sangat kecil dan memiliki terlalu banyak ember. Juga, jika kinerja tidak menjadi masalah, maka rantai if / else juga tidak buruk, jika disederhanakan dengan memfaktorkan this->_car.getAbsoluteAngle()dengan var tmp, dan menghapus perbandingan yang berlebihan dari masing-masing if()klausa OP (memeriksa sesuatu yang sudah cocok if ()) sebelumnya. Atau gunakan saran sort-array @ wilx.
Peter Cordes
58

Buat sebuah array, yang setiap elemennya dikaitkan dengan blok 30 derajat:

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

Kemudian Anda dapat mengindeks array dengan sudut / 30:

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

Tidak perlu perbandingan atau percabangan.

Namun hasilnya sedikit melenceng dari aslinya. Nilai di perbatasan, yaitu 30, 60, 120, dll. Ditempatkan di kategori berikutnya. Misalnya, dalam kode asli nilai yang valid UP_RIGHTadalah 31 hingga 60. Kode di atas menetapkan 30 hingga 59 keUP_RIGHT .

Kita bisa menyiasatinya dengan mengurangi 1 dari sudut:

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

Ini sekarang memberi kita RIGHT30,UP_RIGHT 60, dll.

Dalam kasus 0, ekspresi menjadi (-1 % 360) / 30. Ini valid karena -1 % 360 == -1dan-1 / 30 == 0 , jadi kami masih mendapatkan indeks 0.

Bagian 5.6 dari standar C ++ mengonfirmasi perilaku ini:

4/ Operator biner menghasilkan hasil bagi, dan %operator biner menghasilkan sisa dari pembagian ekspresi pertama dengan yang kedua. Jika operan kedua dari /atau %nol, perilakunya tidak ditentukan. Untuk operan integral, /operator menghasilkan hasil bagi aljabar dengan bagian pecahan apa pun yang dibuang. jika hasil bagi a/bdapat diwakili dalam jenis hasil, (a/b)*b + a%bsama dengan a.

EDIT:

Ada banyak pertanyaan yang diajukan tentang keterbacaan dan pemeliharaan konstruksi seperti ini. Jawaban yang diberikan oleh motoDrizzt adalah contoh yang baik untuk menyederhanakan konstruksi asli yang lebih mudah dipelihara dan tidak terlalu "jelek".

Memperluas jawabannya, berikut adalah contoh lain yang menggunakan operator terner. Karena setiap kasus di posting asli ditetapkan ke variabel yang sama, menggunakan operator ini dapat membantu meningkatkan keterbacaan lebih lanjut.

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.dll
sumber
49

Kode itu tidak jelek, sederhana, praktis, mudah dibaca dan mudah dimengerti. Itu akan diisolasi dengan metodenya sendiri, jadi tidak ada yang harus menghadapinya dalam kehidupan sehari-hari. Dan untuk berjaga-jaga jika seseorang harus memeriksanya -mungkin karena dia sedang men-debug aplikasi Anda untuk masalah di tempat lain- sangat mudah sehingga dia membutuhkan waktu dua detik untuk memahami kode dan fungsinya.

Jika saya melakukan debug seperti itu, saya akan senang tidak menghabiskan lima menit mencoba memahami apa fungsi fungsi Anda. Dalam hal ini, semua fungsi lain gagal sepenuhnya, karena mereka mengubah rutinitas bebas bug yang sederhana, lupakan saja, dalam kekacauan yang rumit sehingga orang-orang saat men-debug akan dipaksa untuk menganalisis dan menguji secara mendalam. Sebagai manajer proyek, saya sendiri sangat kecewa dengan pengembang yang mengambil tugas sederhana dan alih-alih menerapkannya dengan cara yang sederhana dan tidak berbahaya, malah membuang waktu untuk menerapkannya dengan cara yang terlalu rumit. Pikirkan saja semua waktu yang Anda sia-siakan untuk memikirkannya, lalu datang ke SO bertanya, dan semuanya hanya demi pemeliharaan dan keterbacaan yang semakin buruk.

Meskipun demikian, ada kesalahan umum dalam kode Anda yang membuatnya kurang dapat dibaca, dan beberapa peningkatan dapat Anda lakukan dengan mudah:

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;

Letakkan ini ke dalam metode, tetapkan nilai yang dikembalikan ke objek, ciutkan metode, dan lupakan selama sisa waktu.

PS ada bug lain di atas ambang 330, tetapi saya tidak tahu bagaimana Anda ingin mengobatinya, jadi saya tidak memperbaikinya sama sekali.


Pembaruan nanti

Sesuai komentar, Anda bahkan dapat menyingkirkan yang lain jika:

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;

Saya tidak melakukannya karena saya merasa bahwa poin tertentu itu hanya menjadi masalah preferensi sendiri, dan cakupan jawaban saya adalah (dan sedang) memberikan perspektif yang berbeda untuk kekhawatiran Anda tentang "keburukan kode". Bagaimanapun, seperti yang saya katakan, seseorang menunjukkannya di komentar dan saya pikir masuk akal untuk menunjukkannya.

motoDrizzt
sumber
1
Apa yang seharusnya dicapai itu?
Abstraksi adalah segalanya.
8
Jika Anda ingin pergi ke rute ini, Anda setidaknya harus menyingkirkan yang tidak perlu else if, ifsudah cukup.
mulai
10
@ Ðаn Saya sangat tidak setuju tentang else if. Saya merasa berguna untuk dapat melihat sekilas blok kode, dan melihat bahwa itu adalah pohon keputusan, daripada sekelompok pernyataan yang tidak terkait. Ya, elseatau breaktidak diperlukan untuk compiler setelah a return, tetapi berguna bagi orang yang melihat sekilas kode.
IMSoP
@ Ðаn Saya belum pernah melihat bahasa yang memerlukan penyarangan tambahan di sana. Entah ada elseif/ elsifkata kunci yang terpisah , atau Anda secara teknis menggunakan blok satu pernyataan yang kebetulan dimulai if, seperti di sini. Contoh cepat dari apa yang menurut saya sedang Anda pikirkan, dan apa yang saya pikirkan: gist.github.com/IMSoP/90bc1e9e2c56d8314413d7347e76532a
IMSoP
7
@ Ðаn Ya, saya setuju itu akan sangat buruk. Tapi itu bukan elsemembuat Anda melakukan itu, itu adalah panduan gaya yang buruk yang tidak dikenali else ifsebagai pernyataan yang berbeda. Saya akan selalu menggunakan kawat gigi, tetapi saya tidak akan pernah menulis kode seperti itu, seperti yang saya tunjukkan dalam inti saya.
IMSoP
39

Dalam pseudocode:

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

Jadi, kita memiliki 0-60, 60-90, 90-150, ... sebagai kategori. Di setiap kuadran 90 derajat, satu bagian memiliki 60, satu bagian memiliki 30. Jadi, sekarang:

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;

Gunakan indeks dalam larik yang berisi enum dalam urutan yang sesuai.

Bijay Gurung
sumber
7
Ini bagus, mungkin jawaban terbaik di sini. Kemungkinannya adalah jika penanya asli melihat penggunaan enumnya nanti, dia akan menemukan bahwa dia memiliki kasus di mana itu hanya diubah kembali menjadi angka! Menghilangkan enum sepenuhnya dan hanya menggunakan integer arah mungkin masuk akal dengan tempat lain dalam kodenya dan jawaban ini membawa Anda langsung ke sana.
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
sumber
sudut = ini -> _ car.getAbsoluteAngle (); sektor = (sudut% 360) / 30; Hasilnya 12 sektor. Kemudian indeks ke dalam array, atau gunakan switch / case seperti di atas - yang kompiler berubah menjadi tabel lompat.
ChuckCottrill
1
Beralih tidak lebih baik daripada rantai if / else.
Bill K
5
@BillK: Mungkin saja, jika compiler mengubahnya menjadi tabel pencarian untuk Anda. Itu lebih mungkin dibandingkan dengan rantai if / else. Tetapi karena mudah dan tidak memerlukan trik khusus arsitektur apa pun, mungkin yang terbaik adalah menulis pencarian tabel di kode sumber.
Peter Cordes
Secara umum kinerja seharusnya tidak menjadi perhatian - ini adalah keterbacaan dan pemeliharaan - setiap rantai sakelar & if / else biasanya berarti sekumpulan kode salin dan tempel yang berantakan yang harus diperbarui di banyak tempat setiap kali Anda menambahkan item baru. Sebaiknya hindari keduanya dan coba kirim tabel, penghitungan, atau cukup muat data dari file dan perlakukan sebagai data jika Anda bisa.
Bill K
PeterCordes, kompilator kemungkinan besar akan menghasilkan kode yang identik untuk LUT seperti untuk sakelar. @BillK Anda dapat mengekstrak sakelar ke fungsi 0..12 -> Car :: EDirection, yang akan memiliki pengulangan yang setara dengan LUT
Caleth
16

Mengabaikan kasus pertama Anda ifyang merupakan kasus khusus, sisanya mengikuti pola yang sama persis: min, maks, dan arah; pseudo-code:

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

Membuat C ++ nyata ini mungkin terlihat seperti:

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

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

Sekarang, daripada menulis banyak if, cukup ulangi berbagai kemungkinan Anda:

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 pengecualian daripada returningNONE adalah pilihan lain).

Yang kemudian akan Anda panggil:

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

Teknik ini dikenal sebagai pemrograman berbasis data . Selain menghilangkan sekelompok ifs, ini akan memungkinkan Anda untuk dengan mudah menambahkan lebih banyak arah (misalnya, NNW) atau mengurangi nomor (kiri, kanan, atas, bawah) tanpa mengulang kode lain.


(Menangani kasus khusus pertama Anda dibiarkan sebagai "latihan untuk pembaca." :-))

Ðаn
sumber
1
Secara teknis, Anda bisa menghilangkan min karena semua rentang sudut cocok, yang akan mengurangi kondisi menjadi if(angle <= angleRange.max)tetapi +1 untuk menggunakan fitur C ++ 11 seperti enum class.
Pharap
12

Meskipun varian yang diusulkan berdasarkan tabel pencarian angle / 30mungkin lebih disukai, berikut adalah alternatif yang menggunakan pencarian biner berkode keras untuk meminimalkan jumlah perbandingan.

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
sumber
2

Jika Anda benar-benar ingin menghindari duplikasi, Anda dapat mengekspresikannya sebagai rumus matematika.

Pertama-tama, asumsikan bahwa kita menggunakan Enum @ Geek

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

Sekarang kita dapat menghitung enum menggunakan matematika integer (tanpa membutuhkan array).

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

Seperti yang ditunjukkan @motoDrizzt, kode yang ringkas belum tentu merupakan kode yang dapat dibaca. Itu memang memiliki keuntungan kecil yang mengekspresikannya sebagai matematika membuatnya eksplisit bahwa beberapa arah mencakup busur yang lebih luas. Jika Anda ingin pergi ke arah ini, Anda dapat menambahkan beberapa pernyataan untuk membantu memahami kode.

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

Setelah menambahkan asserts Anda telah menambahkan duplikasi, tetapi duplikasi dalam asserts tidak terlalu buruk. Jika Anda memiliki pernyataan yang tidak konsisten, Anda akan segera mengetahuinya. Pernyataan dapat dikompilasi dari versi rilis agar tidak membengkak dieksekusi yang Anda distribusikan. Namun demikian, pendekatan ini mungkin paling dapat diterapkan jika Anda ingin mengoptimalkan kode daripada membuatnya tidak terlalu jelek.

gmatht
sumber
1

Saya Terlambat ke pesta, tapi Kita bisa menggunakan bendera enum dan pemeriksaan jangkauan untuk melakukan sesuatu yang rapi.

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

pemeriksaan (pseudo-code), dengan asumsi sudut absolut (antara 0 dan 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
sumber