Làm cách nào để tránh chuỗi if / else if khi phân loại tiêu đề thành 8 hướng?


111

Tôi có mã sau:

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;

Tôi muốn tránh ifchuỗi s; nó thực sự xấu xí. Có một cách viết khác, có thể rõ ràng hơn không?


77
@Oraekia Nó sẽ trông bớt xấu hơn rất nhiều, ít gõ hơn và dễ đọc hơn nếu bạn lướt qua this->_car.getAbsoluteAngle()một lần trước toàn bộ dòng thác.
πάντα ῥεῖ

26
Tất cả những tham chiếu rõ ràng của this( this->) là không cần thiết và không thực sự làm bất cứ điều gì tốt cho khả năng đọc ..
Jesper Juhl

2
@Neil Ghép làm khóa, enum làm giá trị, tra cứu tùy chỉnh lambda.
πάντα ῥεῖ

56
Mã sẽ đỡ xấu hơn rất nhiều nếu không có tất cả các >thử nghiệm đó; chúng không cần thiết, vì mỗi chúng đã được kiểm tra (theo hướng ngược lại) trong ifcâu lệnh trước .
Pete Becker

10
@PeteBecker Đó là một trong những thú cưng của tôi trộm về mã như thế này. Quá nhiều lập trình viên không hiểu else if.
Barmar

Câu trả lời:


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

Đây là cách tôi sẽ làm điều đó. (Theo nhận xét trước đây của tôi).


92
Tôi thực sự nghĩ rằng tôi đã nhìn thấy Mã Konami thay cho enum ở đó trong một giây.
Zano

21
@CodesInChaos: C99 và C ++ có cùng yêu cầu với C #: nếu q = a/br = a%bsau đó q * b + rphải bằng nhau a. Vì vậy, nó là hợp pháp trong C99 cho một phần còn lại là âm. BorgLeader, bạn có thể khắc phục sự cố với (((angle % 360) + 360) % 360) / 30.
Eric Lippert

7
@ericlippert, bạn và kiến ​​thức toán học tính toán của bạn tiếp tục gây ấn tượng.
gregsdennis

33
Điều này rất thông minh, nhưng nó hoàn toàn không thể đọc được và không có khả năng duy trì được nữa, vì vậy tôi không đồng ý rằng nó là một giải pháp tốt cho sự "xấu xí" của bản gốc. Ở đây, tôi đoán là có một yếu tố sở thích cá nhân, nhưng tôi thấy các phiên bản phân nhánh được làm sạch bởi x4u và motoDrizzt cực kỳ thích hợp.
IMSoP

4
@cyanbeam vòng lặp for trong main chỉ là "bản demo", GetDirectionForAnglelà thứ mà tôi đang đề xuất thay thế cho dòng if / else, cả hai đều là O (1) ...
Borgleader 29/05/17

71

Bạn có thể dùng map::lower_bound và lưu trữ giới hạn trên của mỗi góc trong bản đồ.

Ví dụ làm việc bên dưới:

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

Không cần phân chia. Tốt!
O. Jones

17
@ O.Jones: Phép chia theo hằng số thời gian biên dịch khá rẻ, chỉ cần một phép nhân và một số thay đổi. Tôi muốn đi với một trong những table[angle%360/30]câu trả lời vì nó rẻ và không có chi nhánh. Viễn rẻ hơn so với một vòng lặp cây tìm kiếm, nếu biên dịch này để asm đó là tương tự như nguồn. ( std::unordered_mapthường là một bảng băm, nhưng std::mapthường là một cây nhị phân đỏ-đen. Câu trả lời được chấp nhận sử dụng hiệu quả angle%360 / 30như một hàm băm hoàn hảo cho các góc (sau khi sao chép một vài mục nhập và câu trả lời của Bijay thậm chí còn tránh điều đó bằng một phần bù)).
Peter Cordes

2
Bạn có thể sử dụng lower_boundtrên một mảng được sắp xếp. Điều đó sẽ hiệu quả hơn rất nhiều so với a map.
wilx

@PeterCordes tra cứu bản đồ dễ viết và dễ bảo trì. Nếu các phạm vi thay đổi, việc cập nhật mã băm có thể gây ra lỗi và nếu các phạm vi trở nên không đồng nhất, nó có thể bị hỏng. Trừ khi mã đó là quan trọng về hiệu suất, tôi sẽ không bận tâm.
OhJeez

@OhJeez: Chúng đã không đồng nhất, điều này được xử lý bằng cách có cùng giá trị trong nhiều nhóm. Chỉ cần sử dụng một ước số nhỏ hơn để nhận được nhiều nhóm hơn, trừ khi điều đó có nghĩa là sử dụng một ước số rất nhỏ và có quá nhiều nhóm. Ngoài ra, nếu hiệu suất không quan trọng, thì chuỗi if / else cũng không tệ, nếu được đơn giản hóa bằng cách tính thừa số this->_car.getAbsoluteAngle()với var tmp và loại bỏ so sánh thừa khỏi mỗi if()mệnh đề của OP (kiểm tra xem có điều gì đó đã khớp trước if ()). Hoặc sử dụng gợi ý mảng được sắp xếp của @ wilx.
Peter Cordes

58

Tạo một mảng, mỗi phần tử trong số đó được liên kết với một khối 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
};

Sau đó, bạn có thể lập chỉ mục mảng với góc / 30:

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

Không cần so sánh hoặc phân nhánh.

Tuy nhiên, kết quả hơi khác so với ban đầu. Các giá trị trên đường viền, tức là 30, 60, 120, v.v. được đặt trong danh mục tiếp theo. Ví dụ: trong mã gốc, các giá trị hợp lệ UP_RIGHTlà 31 đến 60. Đoạn mã trên gán 30 đến 59 cho UP_RIGHT.

Chúng ta có thể giải quyết vấn đề này bằng cách trừ đi 1 cho góc:

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

Điều này hiện mang lại cho chúng tôi RIGHT30, UP_RIGHT60, v.v.

Trong trường hợp 0, biểu thức trở thành (-1 % 360) / 30. Điều này hợp lệ bởi vì -1 % 360 == -1-1 / 30 == 0, vì vậy chúng tôi vẫn nhận được chỉ mục là 0.

Phần 5.6 của tiêu chuẩn C ++ xác nhận hành vi này:

4/ Toán tử nhị phân sinh ra thương số và %toán tử nhị phân sinh ra phần còn lại từ phép chia biểu thức đầu tiên cho biểu thức thứ hai. Nếu toán hạng thứ hai của /hoặc %bằng 0 thì hành vi không được xác định. Đối với toán hạng tích phân, toán /tử mang lại thương số đại số với bất kỳ phần phân số nào bị loại bỏ. nếu thương số a/bcó thể biểu diễn trong kiểu kết quả, (a/b)*b + a%bthì bằng a.

BIÊN TẬP:

Có rất nhiều câu hỏi được đặt ra liên quan đến khả năng đọc và khả năng bảo trì của một cấu trúc như thế này. Câu trả lời được đưa ra bởi motoDrizzt là một ví dụ điển hình về việc đơn giản hóa cấu trúc ban đầu dễ bảo trì hơn và không hoàn toàn là "xấu xí".

Mở rộng câu trả lời của anh ấy, đây là một ví dụ khác về việc sử dụng toán tử bậc ba. Vì mỗi trường hợp trong bài đăng gốc được gán cho cùng một biến, việc sử dụng toán tử này có thể giúp tăng khả năng đọc hơn nữa.

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

Mã đó không xấu, nó đơn giản, thiết thực, dễ đọc và dễ hiểu. Nó sẽ được cô lập theo phương pháp riêng của nó, vì vậy sẽ không ai phải đối phó với nó trong cuộc sống hàng ngày. Và đề phòng trường hợp ai đó phải kiểm tra nó - có thể vì anh ta đang gỡ lỗi ứng dụng của bạn cho một vấn đề ở nơi khác - thật dễ dàng, anh ta sẽ mất hai giây để hiểu mã và nó làm gì.

Nếu tôi đang thực hiện gỡ lỗi như vậy, tôi rất vui vì không phải dành năm phút để cố gắng hiểu chức năng của bạn làm gì. Về mặt này, tất cả các chức năng khác đều thất bại hoàn toàn, vì chúng thay đổi một quy trình đơn giản, hay quên, không có lỗi, trong một mớ hỗn độn phức tạp mà mọi người khi gỡ lỗi sẽ buộc phải phân tích sâu và kiểm tra. Với tư cách là người quản lý dự án, tôi thực sự rất khó chịu khi một nhà phát triển nhận một nhiệm vụ đơn giản và thay vì triển khai nó một cách đơn giản, vô hại, lại lãng phí thời gian để thực hiện nó một cách quá phức tạp. Chỉ cần suy nghĩ về tất cả thời gian bạn đã lãng phí suy nghĩ về nó, sau đó đến SO hỏi, và tất cả chỉ vì lợi ích của việc bảo trì và khả năng đọc của nó tồi tệ hơn.

Điều đó nói rằng, có một lỗi phổ biến trong mã của bạn khiến nó trở nên khó đọc hơn và một vài cải tiến bạn có thể thực hiện khá dễ dàng:

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;

Đặt điều này vào một phương thức, gán giá trị trả về cho đối tượng, thu gọn phương thức và quên nó trong phần còn lại của vĩnh viễn.

Tái bút có một lỗi khác vượt ngưỡng 330, nhưng không biết bạn muốn xử lý như thế nào nên không sửa được.


Cập nhật sau

Theo nhận xét, bạn thậm chí có thể loại bỏ cái khác nếu ở tất cả:

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;

Tôi đã không làm điều đó vì tôi cảm thấy rằng một điểm nào đó nó trở thành vấn đề của sở thích riêng và phạm vi câu trả lời của tôi là (và đang) đưa ra một góc nhìn khác cho mối quan tâm của bạn về "sự xấu xí của mã". Dù sao, như tôi đã nói, ai đó đã chỉ ra điều đó trong các bình luận và tôi nghĩ việc thể hiện điều đó là rất hợp lý.


1
Cái quái gì mà phải đạt được?
Trừu tượng là tất cả.

8
Nếu bạn muốn đi con đường này, bạn nên loại bỏ những thứ không cần thiết else if, iflà đủ.
Ðаn

10
@ Ðаn Tôi hoàn toàn không đồng ý về else if. Tôi thấy hữu ích khi có thể xem qua một khối mã và thấy rằng đó là một cây quyết định, thay vì một nhóm các câu lệnh không liên quan. Có, elsehoặc breakkhông cần thiết cho trình biên dịch sau a return, nhưng chúng hữu ích cho những người đang nhìn lướt qua mã.
IMSoP

@ Ðà tôi chưa bao giờ thấy một ngôn ngữ mà ở đó cần có thêm bất kỳ lồng ghép nào. Hoặc có một từ khóa elseif/ riêng biệt elsifhoặc về mặt kỹ thuật bạn đang sử dụng khối một câu lệnh sẽ bắt đầu if, giống như ở đây. Ví dụ nhanh về những gì tôi nghĩ bạn đang nghĩ đến và những gì tôi đang nghĩ đến: gist.github.com/IMSoP/90bc1e9e2c56d8314413d7347e76532a
IMSoP

7
@ Ðаn Vâng, tôi đồng ý rằng điều đó sẽ rất kinh khủng. Nhưng đó không phải là elseviệc khiến bạn làm điều đó, mà đó là một hướng dẫn phong cách nghèo nàn không được công nhận else iflà một tuyên bố khác biệt. Tôi sẽ luôn sử dụng dấu ngoặc nhọn, nhưng tôi sẽ không bao giờ viết mã như vậy, như tôi đã trình bày trong ý chính của mình.
IMSoP

39

Trong mã giả:

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

Vì vậy, chúng tôi có 0-60, 60-90, 90-150, ... như các loại. Trong mỗi góc phần tư có 90 độ, một phần có 60, một phần có 30. Vì vậy, bây giờ:

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;

Sử dụng chỉ mục trong một mảng chứa các enum theo thứ tự thích hợp.


7
Đây là tốt, có lẽ là câu trả lời tốt nhất ở đây. Rất có thể là nếu người hỏi ban đầu xem xét việc sử dụng enum sau này, anh ta sẽ thấy rằng anh ta gặp trường hợp nó chỉ được chuyển đổi lại thành một số! Loại bỏ hoàn toàn enum và chỉ gắn với một số nguyên hướng có thể có ý nghĩa với các vị trí khác trong mã của anh ấy và câu trả lời này giúp bạn trực tiếp đến đó.
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;
}

angle = this -> _ car.getAbsoluteAngle (); khu vực = (góc% 360) / 30; Kết quả là 12 lĩnh vực. Sau đó lập chỉ mục thành mảng, hoặc sử dụng switch / case như trên - trình biên dịch nào cũng chuyển thành bảng nhảy.
ChuckCottrill

1
Chuyển đổi không thực sự tốt hơn chuỗi if / else.
Bill K

5
@BillK: Có thể là vậy, nếu trình biên dịch biến nó thành bảng tra cứu cho bạn. Điều đó có nhiều khả năng hơn so với chuỗi if / else. Nhưng vì nó dễ dàng và không yêu cầu bất kỳ thủ thuật dành riêng cho kiến ​​trúc nào, nên có lẽ tốt nhất bạn nên viết bảng tra cứu trong mã nguồn.
Peter Cordes

Nói chung hiệu suất không phải là mối quan tâm - đó là tính dễ đọc và khả năng bảo trì - mỗi chuỗi chuyển đổi & if / else thường có nghĩa là một loạt các mã sao chép và dán lộn xộn phải được cập nhật ở nhiều nơi bất cứ khi nào bạn thêm một mục mới. Tốt nhất nên tránh cả hai và thử các bảng điều phối, tính toán hoặc chỉ tải dữ liệu từ một tệp và coi nó như dữ liệu nếu bạn có thể.
Bill K

PeterCordes trình biên dịch có khả năng tạo mã giống hệt nhau cho LUT như cho công tắc. @BillK, bạn có thể giải nén công tắc vào một hàm 0..12 -> Car :: EDirection, sẽ có sự lặp lại tương đương với LUT
Caleth

16

Bỏ qua iftrường hợp đầu tiên của bạn là trường hợp hơi đặc biệt, những cái còn lại đều tuân theo cùng một mẫu chính xác: min, max và hướng; mã giả:

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

Tạo C ++ thực này có thể trông giống như:

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

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

Bây giờ, thay vì viết một loạt các ifs, chỉ cần lặp lại các khả năng khác nhau của bạn:

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 một ngoại lệ thay vì returning NONElà một tùy chọn khác).

Sau đó bạn sẽ gọi:

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

Kỹ thuật này được gọi là lập trình hướng dữ liệu . Bên cạnh việc loại bỏ một loạt các ifs, nó sẽ cho phép bạn dễ dàng sử dụng thêm các chỉ đường (ví dụ: NNW) hoặc giảm số lượng (trái, phải, lên, xuống) mà không cần làm lại mã khác.


(Xử lý trường hợp đặc biệt đầu tiên của bạn được để như "một bài tập cho người đọc." :-))


1
Về mặt kỹ thuật, bạn có thể loại bỏ tối thiểu khi tất cả các phạm vi góc khớp với nhau, điều này sẽ giảm điều kiện xuống if(angle <= angleRange.max)còn +1 đối với việc sử dụng các tính năng C ++ 11 như enum class.
Pharap

12

Mặc dù các biến thể được đề xuất dựa trên bảng tra cứu angle / 30có lẽ là thích hợp hơn, nhưng đây là một giải pháp thay thế sử dụng tìm kiếm nhị phân được mã hóa cứng để giảm thiểu số lượng so sánh.

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

Nếu bạn thực sự muốn tránh trùng lặp, bạn có thể biểu thị điều này dưới dạng công thức toán học.

Trước hết, giả sử rằng chúng ta đang sử dụng @ Geek's Enum

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

Bây giờ chúng ta có thể tính toán enum bằng cách sử dụng toán học số nguyên (không cần đến mảng).

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

Như @motoDrizzt đã chỉ ra, mã ngắn gọn không nhất thiết phải là mã có thể đọc được. Nó có một lợi thế nhỏ là thể hiện nó dưới dạng toán học làm cho nó rõ ràng rằng một số hướng bao phủ một cung rộng hơn. Nếu bạn muốn đi theo hướng này, bạn có thể thêm một số xác nhận để giúp hiểu mã.

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

Sau khi thêm xác nhận, bạn đã thêm trùng lặp, nhưng trùng lặp trong xác nhận không quá tệ. Nếu bạn có một khẳng định không nhất quán, bạn sẽ sớm phát hiện ra. Các cảnh báo có thể được biên dịch từ phiên bản phát hành để không làm cồng kềnh tệp thực thi mà bạn phân phối. Tuy nhiên, cách tiếp cận này có lẽ áp dụng nhất nếu bạn muốn tối ưu hóa mã hơn là chỉ làm cho nó bớt xấu xí.


1

Tôi đến muộn bữa tiệc, nhưng Chúng tôi có thể sử dụng cờ enum và kiểm tra phạm vi để thực hiện một điều gì đó gọn gàng.

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

kiểm tra (mã giả), giả định góc là tuyệt đối (từ 0 đến 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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.