จะหลีกเลี่ยง if / else if chain ได้อย่างไรเมื่อจำแนกหัวเรื่องออกเป็น 8 ทิศทาง?


111

ฉันมีรหัสต่อไปนี้:

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;

ฉันต้องการหลีกเลี่ยงifโซ่ s; มันน่าเกลียดจริงๆ มีวิธีอื่นที่น่าจะสะอาดกว่านี้ในการเขียนหรือไม่?


77
@Oraekiaมันจะดูน่าเกลียดน้อยกว่ามากพิมพ์น้อยลงและอ่านได้ดีกว่าถ้าคุณอ่านthis->_car.getAbsoluteAngle()ครั้งเดียวก่อนที่จะเรียงซ้อนกันทั้งหมด
πάνταῥεῖ

26
การอ้างอิงที่ชัดเจนของthis( this->) ทั้งหมดนั้นไม่จำเป็นและไม่ได้ส่งผลดีต่อความสามารถในการอ่านเลย ..
Jesper Juhl

2
@Neil จับคู่เป็นคีย์ enum เป็นค่าแลมด้าค้นหาแบบกำหนดเอง
πάνταῥεῖ

56
รหัสจะน่าเกลียดน้อยลงมากหากไม่มี>การทดสอบเหล่านั้นทั้งหมด พวกเขาไม่จำเป็นเนื่องจากแต่ละคนได้รับการทดสอบแล้ว (ในทิศทางตรงกันข้าม) ในifคำสั่งก่อนหน้านี้
Pete Becker

10
@PeteBecker นั่นเป็นสัตว์เลี้ยงตัวหนึ่งของฉันเกี่ยวกับรหัสแบบนี้ else ifเกินไปโปรแกรมเมอร์จำนวนมากไม่เข้าใจ
Barmar

คำตอบ:


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

นี่คือวิธีที่ฉันจะทำ (ตามความคิดเห็นก่อนหน้าของฉัน)


92
แท้จริงฉันคิดว่าฉันเห็นรหัสโคนามิแทนที่ enum ที่นั่นเป็นครั้งที่สอง
Zano

21
@CodesInChaos: C99 และ C ++ มีความต้องการเช่นเดียวกับ C #: ว่าถ้าq = a/bและr = a%bแล้วต้องเท่ากับq * b + r aดังนั้นจึงถูกกฎหมายใน C99 ที่ส่วนที่เหลือจะเป็นลบ BorgLeader คุณสามารถแก้ไขปัญหาด้วย(((angle % 360) + 360) % 360) / 30.
Eric Lippert

7
@ericlippert คุณและความรู้คณิตศาสตร์เชิงคำนวณของคุณยังคงสร้างความประทับใจ
gregsdennis

33
นี่เป็นเรื่องที่ฉลาดมาก แต่อ่านไม่ออกโดยสิ้นเชิงและไม่มีแนวโน้มที่จะดูแลรักษาได้อีกต่อไปดังนั้นฉันจึงไม่เห็นด้วยว่าเป็นวิธีแก้ปัญหาที่ดีสำหรับการรับรู้ "ความน่าเกลียด" ของต้นฉบับ ฉันเดาว่ามีองค์ประกอบของรสนิยมส่วนตัวอยู่ที่นี่ แต่ฉันพบว่าเวอร์ชันที่แยกย่อยโดย x4u และ motoDrizzt เป็นที่นิยมอย่างมาก
IMSoP

4
@cyanbeam สำหรับลูปใน main เป็นเพียง "การสาธิต" GetDirectionForAngleคือสิ่งที่ฉันเสนอเพื่อทดแทนน้ำตก if / else พวกเขาทั้งคู่ O (1) ...
Borgleader

71

คุณสามารถใช้map::lower_boundและจัดเก็บขอบเขตบนของแต่ละมุมในแผนที่

ตัวอย่างการทำงานด้านล่าง:

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

ไม่จำเป็นต้องแบ่ง ดี!
O. Jones

17
@ O Jones: การหารด้วยค่าคงที่เวลาคอมไพล์นั้นค่อนข้างถูกเพียงแค่การคูณและการเปลี่ยนแปลงบางอย่าง ฉันจะtable[angle%360/30]ตอบคำถามข้อหนึ่งเพราะราคาถูกและไม่มีสาขา ไกลราคาถูกกว่าห่วงต้นไม้ค้นหาถ้าคอมไพล์นี้เพื่อ asm ที่คล้ายกับแหล่งที่มา ( std::unordered_mapโดยปกติจะเป็นตารางแฮช แต่std::mapโดยทั่วไปแล้วจะเป็นต้นไม้ไบนารีสีแดง - ดำคำตอบที่ได้รับการยอมรับจะใช้angle%360 / 30เป็นฟังก์ชันแฮชที่สมบูรณ์แบบสำหรับมุมได้อย่างมีประสิทธิภาพ(หลังจากจำลองรายการสองรายการและคำตอบของ Bijay ยังหลีกเลี่ยงสิ่งนั้นด้วยการชดเชย))
Peter Cordes

2
คุณสามารถใช้lower_boundกับอาร์เรย์ที่เรียงลำดับ นั่นจะมีประสิทธิภาพมากกว่าไฟล์map.
wilx

การค้นหาแผนที่ @PeterCordes นั้นเขียนง่ายและดูแลรักษาง่าย หากช่วงมีการเปลี่ยนแปลงการอัปเดตโค้ดแฮชอาจทำให้เกิดข้อบกพร่องและหากช่วงไม่สม่ำเสมอก็อาจแตกออกได้ เว้นแต่ว่ารหัสนั้นมีความสำคัญต่อประสิทธิภาพฉันก็ไม่ต้องกังวล
OhJeez

@OhJeez: พวกมันไม่เหมือนกันอยู่แล้วซึ่งได้รับการจัดการโดยมีค่าเดียวกันในหลายที่เก็บข้อมูล เพียงแค่ใช้ตัวหารขนาดเล็กเพื่อให้ได้บัคเก็ตมากขึ้นเว้นแต่นั่นจะหมายถึงการใช้ตัวหารขนาดเล็กมากและมีบัคเก็ตมากเกินไป นอกจากนี้หากประสิทธิภาพไม่สำคัญเชน if / else ก็ไม่เลวเช่นกันหากทำให้ง่ายขึ้นโดยการแยกthis->_car.getAbsoluteAngle()ตัวประกอบด้วย tmp var และลบการเปรียบเทียบที่ซ้ำซ้อนออกจากแต่ละส่วนของ OP if()(ตรวจสอบสิ่งที่น่าจะตรงกันแล้ว ก่อนหน้า if ()) หรือใช้คำแนะนำอาร์เรย์แบบเรียงลำดับของ @ wilx
Peter Cordes

58

สร้างอาร์เรย์ซึ่งแต่ละองค์ประกอบเชื่อมโยงกับบล็อก 30 องศา:

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

จากนั้นคุณสามารถทำดัชนีอาร์เรย์ด้วยมุม / 30:

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

ไม่จำเป็นต้องมีการเปรียบเทียบหรือแยกสาขา

อย่างไรก็ตามผลลัพธ์จะผิดไปจากเดิมเล็กน้อย ค่าบนพรมแดนเช่น 30, 60, 120 เป็นต้นจะอยู่ในหมวดหมู่ถัดไป ยกตัวอย่างเช่นในรหัสเดิมค่าที่ถูกต้องสำหรับUP_RIGHT31 ถึง 60 กำหนดรหัสข้าง 30-59 UP_RIGHTไป

เราสามารถหลีกเลี่ยงสิ่งนี้ได้โดยการลบ 1 จากมุม:

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

ตอนนี้ให้เราRIGHTเป็น 30, UP_RIGHT60 เป็นต้น

ในกรณีของ 0 (-1 % 360) / 30แสดงออกจะกลายเป็น สิ่งนี้ถูกต้องเนื่องจาก-1 % 360 == -1และ-1 / 30 == 0ดังนั้นเรายังคงได้รับดัชนีเป็น 0

มาตรา 5.6 ของมาตรฐาน C ++ยืนยันพฤติกรรมนี้:

4ตัว/ดำเนินการไบนารีให้ผลหารและตัว%ดำเนินการไบนารีจะให้ผลที่เหลือจากการหารนิพจน์แรกด้วยตัวที่สอง ถ้าตัวถูกดำเนินการที่สอง/หรือ%เป็นศูนย์พฤติกรรมจะไม่ได้กำหนดไว้ สำหรับอินทิกรัลโอเป/อเรเตอร์ตัวดำเนินการจะให้ผลหารพีชคณิตพร้อมกับเศษส่วนใด ๆ ที่ทิ้งไป ถ้าหารa/bเป็นแทนได้ในรูปแบบของผลที่ จะมีค่าเท่ากับ(a/b)*b + a%ba

แก้ไข:

มีคำถามมากมายเกี่ยวกับความสามารถในการอ่านและการบำรุงรักษาของโครงสร้างเช่นนี้ คำตอบที่ได้รับจาก motoDrizzt เป็นตัวอย่างที่ดีในการทำให้โครงสร้างดั้งเดิมง่ายขึ้นซึ่งสามารถดูแลรักษาได้มากกว่าและไม่ "น่าเกลียด"

ขยายคำตอบของเขาต่อไปนี้เป็นอีกตัวอย่างหนึ่งในการใช้ตัวดำเนินการ ternary เนื่องจากแต่ละกรณีในโพสต์ต้นฉบับกำหนดให้กับตัวแปรเดียวกันการใช้โอเปอเรเตอร์นี้สามารถช่วยเพิ่มความสามารถในการอ่านเพิ่มเติม

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;

49

รหัสนั้นไม่น่าเกลียดง่ายใช้งานได้จริงอ่านง่ายและเข้าใจง่าย มันจะถูกแยกออกด้วยวิธีการของตัวเองดังนั้นจะไม่มีใครต้องจัดการกับมันในชีวิตประจำวัน และในกรณีที่มีคนตรวจสอบ - อาจเป็นเพราะเขากำลังแก้ไขข้อบกพร่องของแอปพลิเคชันของคุณเพื่อหาปัญหาที่อื่นมันง่ายมากเขาจะใช้เวลาสองวินาทีในการทำความเข้าใจรหัสและสิ่งที่มันทำ

หากฉันกำลังทำการดีบักฉันยินดีที่จะไม่ต้องเสียเวลาห้านาทีในการทำความเข้าใจว่าฟังก์ชันของคุณทำอะไร ในเรื่องนี้ฟังก์ชันอื่น ๆ ทั้งหมดจะล้มเหลวโดยสิ้นเชิงเนื่องจากพวกเขาเปลี่ยนกิจวัตรที่เรียบง่ายลืมมันไม่มีจุดบกพร่องในความยุ่งเหยิงที่ซับซ้อนซึ่งผู้คนเมื่อทำการดีบั๊กจะถูกบังคับให้วิเคราะห์และทดสอบอย่างลึกซึ้ง ในฐานะผู้จัดการโครงการเองฉันรู้สึกไม่พอใจอย่างมากที่นักพัฒนาใช้งานง่ายๆและแทนที่จะนำไปใช้ในวิธีที่ง่ายและไม่เป็นอันตรายเสียเวลาในการนำไปใช้ในวิธีที่ซับซ้อนเกินไป เพียงแค่คิดตลอดเวลาที่คุณเสียเวลาคิดเกี่ยวกับเรื่องนี้จากนั้นจึงมาถาม SO และทั้งหมดนี้ก็เพื่อประโยชน์ในการบำรุงรักษาและความสามารถในการอ่านที่แย่ลง

ที่กล่าวว่ามีข้อผิดพลาดทั่วไปในโค้ดของคุณที่ทำให้อ่านได้น้อยลงและมีการปรับปรุงสองสามอย่างที่คุณสามารถทำได้อย่างง่ายดาย:

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;

ใส่สิ่งนี้ลงในวิธีการกำหนดค่าที่ส่งคืนให้กับวัตถุยุบเมธอดและลืมมันไปชั่วนิรันดร์

ปล. มีข้อผิดพลาดอื่นที่เกินเกณฑ์ 330 แต่ฉันไม่รู้ว่าคุณต้องการรักษาอย่างไรฉันจึงไม่ได้แก้ไขเลย


อัปเดตในภายหลัง

ตามความคิดเห็นคุณสามารถกำจัดสิ่งอื่นได้หาก:

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;

ฉันไม่ได้ทำเพราะฉันรู้สึกว่าจุดหนึ่งที่มันกลายเป็นเรื่องของความชอบของตัวเองและขอบเขตของคำตอบของฉันคือ (และ) เพื่อให้มุมมองที่แตกต่างกับความกังวลของคุณเกี่ยวกับ "ความอัปลักษณ์ของโค้ด" อย่างไรก็ตามอย่างที่ฉันพูดมีคนชี้ให้เห็นในความคิดเห็นและฉันคิดว่ามันสมเหตุสมผลที่จะแสดงมัน


1
สิ่งที่ควรจะบรรลุคืออะไร?
นามธรรมคือทุกสิ่ง

8
หากคุณต้องการไปเส้นทางนี้อย่างน้อยคุณควรกำจัดสิ่งที่ไม่จำเป็นelse ifออกไปifก็เพียงพอแล้ว
60

10
@ ฉันไม่เห็นด้วยอย่างสิ้นเชิงเกี่ยวกับไฟล์else if. ฉันพบว่ามีประโยชน์ในการดูบล็อกโค้ดและเห็นว่าเป็นโครงสร้างการตัดสินใจแทนที่จะเป็นกลุ่มของข้อความที่ไม่เกี่ยวข้อง ใช่elseหรือbreakไม่จำเป็นสำหรับคอมไพเลอร์หลัง a returnแต่มีประโยชน์สำหรับมนุษย์ที่จ้องมองโค้ด
IMSoP

@ Ðаnฉันไม่เคยเห็นภาษาใดที่จะต้องมีการทำรังพิเศษที่นั่น ไม่ว่าจะมีคำหลักelseif/ elsifคำหลักแยกต่างหากหรือในทางเทคนิคคุณใช้บล็อกคำสั่งเดียวที่เกิดขึ้นเพื่อเริ่มต้นifเช่นเดียวกับที่นี่ ตัวอย่างสั้น ๆ ของสิ่งที่ฉันคิดว่าคุณกำลังคิดและสิ่งที่ฉันกำลังคิดอยู่: gist.github.com/IMSoP/90bc1e9e2c56d8314413d7347e76532a
IMSoP

7
@ ใช่ฉันยอมรับว่ามันน่ากลัว แต่ไม่ใช่การelseทำให้คุณทำแบบนั้น แต่เป็นแนวทางที่ไม่ดีซึ่งไม่รู้จักelse ifว่าเป็นคำพูดที่แตกต่างกัน ฉันมักจะใช้วงเล็บปีกกา แต่ฉันจะไม่เขียนโค้ดแบบนั้นอย่างที่ฉันแสดงในส่วนสำคัญของฉัน
IMSoP

39

ใน pseudocode:

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

ดังนั้นเรามี0-60, 60-90, 90-150... เป็นหมวดหมู่ ในแต่ละจตุภาคที่มี 90 องศาส่วนหนึ่งมี 60 ส่วนหนึ่งมี 30 ดังนั้นตอนนี้:

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;

ใช้ดัชนีในอาร์เรย์ที่มี enums ตามลำดับที่เหมาะสม


7
ดีตรงนี้น่าจะเป็นคำตอบที่ดีที่สุด มีโอกาสที่หากผู้ถามเดิมมองไปที่การใช้ enum ของเขาในภายหลังเขาจะพบว่าเขามีกรณีที่เพิ่งถูกแปลงกลับเป็นตัวเลข! การกำจัด enum อย่างสมบูรณ์และเพียงแค่ยึดติดกับจำนวนเต็มทิศทางอาจเหมาะสมกับตำแหน่งอื่น ๆ ในรหัสของเขาและคำตอบนี้จะช่วยให้คุณไปที่นั่นได้โดยตรง
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;
}

มุม = นี้ -> _ car.getAbsoluteAngle (); เซกเตอร์ = (มุม% 360) / 30; ผลลัพธ์คือ 12 ภาค จากนั้นจัดทำดัชนีลงในอาร์เรย์หรือใช้สวิตช์ / ตัวพิมพ์ด้านบนซึ่งคอมไพเลอร์จะแปลงเป็นตารางกระโดด
ChuckCottrill

1
เปลี่ยนไม่ได้ดีไปกว่าโซ่ if / else จริงๆ
Bill K

5
@BillK: อาจเป็นไปได้ถ้าคอมไพเลอร์เปลี่ยนเป็นการค้นหาตารางสำหรับคุณ มีความเป็นไปได้มากกว่าด้วย if / else chain แต่เนื่องจากเป็นเรื่องง่ายและไม่ต้องใช้เทคนิคเฉพาะสถาปัตยกรรมใด ๆ จึงควรเขียนการค้นหาตารางในซอร์สโค้ด
Peter Cordes

โดยทั่วไปแล้วประสิทธิภาพไม่ควรเป็นปัญหาเพราะความสามารถในการอ่านและการบำรุงรักษา - ทุกสวิตช์ & if / else chain มักจะหมายถึงการคัดลอกและวางโค้ดที่ยุ่งเหยิงจำนวนมากซึ่งต้องได้รับการอัปเดตในหลาย ๆ ที่ทุกครั้งที่คุณเพิ่มรายการใหม่ ควรหลีกเลี่ยงทั้งสองอย่างและลองจัดส่งตารางการคำนวณหรือเพียงแค่โหลดข้อมูลจากไฟล์และถือว่าเป็นข้อมูล
Bill K

PeterCordes คอมไพเลอร์มีแนวโน้มที่จะสร้างรหัสที่เหมือนกันสำหรับ LUT สำหรับสวิตช์ @BillK คุณสามารถแยกสวิตช์เป็นฟังก์ชัน 0..12 -> Car :: EDirection ซึ่งจะมีการทำซ้ำเทียบเท่ากับ LUT
Caleth

16

ไม่สนใจข้อแรกของคุณifซึ่งเป็นกรณีพิเศษเล็กน้อยส่วนที่เหลือทั้งหมดเป็นไปตามรูปแบบเดียวกันทั้งหมด: a min, max และ direction; หลอกรหัส:

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

การสร้าง C ++ จริงนี้อาจมีลักษณะดังนี้:

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

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

ตอนนี้แทนที่จะเขียนเป็นพวงifให้วนซ้ำความเป็นไปได้ต่างๆของคุณ:

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มีข้อยกเว้นมากกว่าreturningNONEเป็นอีกทางเลือกหนึ่ง)

ซึ่งคุณจะโทร:

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

เทคนิคนี้เป็นที่รู้จักกันในการเขียนโปรแกรมที่ขับเคลื่อนด้วยข้อมูล นอกจากการกำจัดพวงifแล้วยังช่วยให้คุณสามารถใช้เพิ่มทิศทางได้อย่างง่ายดาย (เช่น NNW) หรือลดจำนวน (ซ้ายขวาขึ้นลง) โดยไม่ต้องทำงานโค้ดอื่นซ้ำ


(การจัดการกรณีพิเศษแรกของคุณถือเป็น "แบบฝึกหัดสำหรับผู้อ่าน" :-))


1
ในทางเทคนิคคุณสามารถกำจัด min ได้เนื่องจากช่วงมุมทั้งหมดตรงกันซึ่งจะลดเงื่อนไขเหลือif(angle <= angleRange.max)แต่ +1 สำหรับการใช้คุณสมบัติ C ++ 11 เช่นenum class.
Pharap

12

แม้ว่ารูปแบบที่เสนอตามตารางการค้นหาangle / 30อาจเป็นที่ต้องการมากกว่า แต่นี่เป็นทางเลือกหนึ่งที่ใช้การค้นหาไบนารีแบบเข้ารหัสเพื่อลดจำนวนการเปรียบเทียบ

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
}

2

หากคุณต้องการหลีกเลี่ยงการทำซ้ำคุณสามารถแสดงสิ่งนี้เป็นสูตรทางคณิตศาสตร์ได้

ก่อนอื่นสมมติว่าเราใช้ Enum ของ @ Geek

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

ตอนนี้เราสามารถคำนวณ enum โดยใช้คณิตศาสตร์จำนวนเต็ม (โดยไม่ต้องใช้อาร์เรย์)

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

ดังที่ @motoDrizzt ชี้ให้เห็นว่ารหัสที่กระชับไม่จำเป็นต้องอ่านรหัสได้ มีข้อได้เปรียบเล็กน้อยที่การแสดงเป็นคณิตศาสตร์ทำให้ชัดเจนว่าบางทิศทางครอบคลุมส่วนโค้งที่กว้างขึ้น หากคุณต้องการไปในทิศทางนี้คุณสามารถเพิ่มคำยืนยันเพื่อช่วยให้เข้าใจโค้ดได้

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

เมื่อเพิ่มการยืนยันแล้วคุณได้เพิ่มการทำสำเนา แต่การทำซ้ำในการยืนยันก็ไม่ได้เลวร้ายนัก หากคุณมีการยืนยันที่ไม่สอดคล้องกันคุณจะพบว่าเร็วพอ Asserts สามารถคอมไพล์จากเวอร์ชันรีลีสเพื่อไม่ให้ไฟล์ปฏิบัติการที่คุณแจกจ่ายขยายตัว อย่างไรก็ตามแนวทางนี้น่าจะใช้ได้มากที่สุดหากคุณต้องการเพิ่มประสิทธิภาพโค้ดแทนที่จะทำให้มันน่าเกลียดน้อยลง


1

ฉันมาสายไปงานปาร์ตี้ แต่เราสามารถใช้ธง enum และการตรวจสอบช่วงเพื่อทำอะไรบางอย่างให้เรียบร้อย

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

การตรวจสอบ (รหัสหลอก) สมมติว่ามุมเป็นค่าสัมบูรณ์ (ระหว่าง 0 ถึง 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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.