제목을 8 방향으로 분류 할 때 체인이 if / else if 경우를 피하는 방법은 무엇입니까?
다음 코드가 있습니다.
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
체인 을 피하고 싶다 ; 정말 못 생겼어를 생성하는 또 다른 다른 더 많은 방법이 있습니까?
#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;
}
이것이 내가하는 방법입니다. (이전 의견에 따라).
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;
}
각 요소가 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_RIGHT
은 31 ~ 60입니다. 위 코드는 30 ~ 59를 할당합니다 UP_RIGHT
.
각도에서 1을 빼서이 문제를 해결할 수 있습니다.
this->_car.edir = dirlist[((this->_car.getAbsoluteAngle() - 1) % 360) / 30];
이제 RIGHT
30, UP_RIGHT
60 등을 제공합니다.
0의 경우식이 (-1 % 360) / 30
됩니다. 이것은 -1 % 360 == -1
및 때문에 유효하므로 -1 / 30 == 0
색인은 여전히 0입니다.
C ++ 표준의 5.6 절 에서 동작을 확인합니다.
4 이항
/
연산자는 몫을 많이하고 이항%
연산자는 첫 번째 단계를 두 번째로 나눈 나머지를 많이 수행합니다. 두 번째 피연산자 가 0/
이거나%
0이면 동작이 정의되지 않은 것입니다. 정수 피연산자의 경우/
연산자는 분수 부분을 버린 대수 지수가 있습니다. 몫이 경우a/b
결과의 타입에서 표현할 수이고,(a/b)*b + a%b
동일하다a
.
편집하다 :
이와 같은 구성의 가독성 및 유지 관리 성과 관련하여 제기 된 많은 질문이 있습니다. motoDrizzt의 답변은 유지 관리가 제 "추악한"것이 아닌 원래 구성을 단순화하는 좋은 예입니다.
그의 대답을 확장하면서 삼항 연산자를 사용하는 또 다른 예가 있습니다. 원래 게시물의 각 사례는 동일한 변수에 할당이 연산자를 사용하여 더 쉽게 사용할 수 있습니다.
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;
그 코드는 추악하지 않고 단순하고 실용적이며 판독하기 쉽습니다. 그것은 자신의 방법으로 격리 될 것입니다. 그리고 누군가가 그것을 확인해야 할 경우를 대비하여-어쩌면 다른 곳에서 문제를 해결하기 위해 응용 프로그램이 있기 때문에 코드와 코드를 이해하는 데 2 초가 걸리기.
그런 디버그를하고 있다면 5 분 동안 함수의 기능을 이해하려고 노력하지 않아도 기쁩니다. 이와 관련하여 거기서 할 때 사람들이 깊이 분석하고해야 할 때 복잡한 혼란 속에서 단순하고 잊지해야 할 때 복잡한 혼란을 겪고 있습니다. 프로젝트 관리자로서 나는 간단한 작업을 수행하는 대신 간단하고 무해한 방식으로 구현하는 대신 개발자가 지나치게 복잡한 방식으로 구현하는 데 시간을 사용할 수있는 화가 날 것입니다. 당신이 사용 가능하다고 생각할 수 있고, 구매할 때마다, 유지 보수와 가독성을 평가할 수 있습니다.
즉, 코드에서는 읽기가 쉽지 않은 일반적인 실수가 일반적으로 몇 가지 개선 사항을 사용하면 쉽게 수행 할 수 있습니다.
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;
나는 그것이 자신의 취향의 문제가된다고 생각하기 때문에 그것을하지 않았다. 그리고 나의 대답의 범위는 "코드의 추악함"에 대한 당신의 우려에 대해 다른 관점을 제시하는 것입니다. 어쨌든, 내가 말했듯이 누군가가 의견을 말했듯이 그것이 합리적이라고 생각합니다.
의사 코드에서 :
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;
배열의 배열을 사용하십시오.
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;
}
if
특별한 경우 인 첫 번째 를 무시 하고 나머지는 모두 동일한 패턴을 약간 있습니다. 최소, 최대 및 방향; 의사 코드 :
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
s를 쓰지 말고 다양한 가능성을 반복하십시오.
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
ing 대신 예외 return
를 사용 하는 NONE
옵션입니다).
그런 다음 전화 할 것입니다 :
_car.edir = direction_from_angle(_car.getAbsoluteAngle(), {
{30, 60, EDirection::UP_RIGHT},
{60, 120, EDirection::UP},
// ... etc.
});
이 기술을 데이터 기반 프로그래밍이라고 합니다. if
s를 제거하는 것 외에도 다른 코드를 다시 작업하지 더 많은 방향 (예 : NNW)을 쉽게 추가하거나 숫자 (왼쪽, 오른쪽, 위, 아래)를 사용할 수 있습니다.
(첫 번째 특별한 경우를 처리하는 것은 "독자를위한 연습"으로 남겨집니다. :-))
룩업 테이블을 기반으로 제안 된 변형 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
}
정말로 중복을 피하고 싶다면 이것을 수학 공식으로 표현할 수 있습니다.
우선 @Geek의 Enum을 사용하고 있다고 가정합니다.
Enum EDirection { RIGHT =0, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT,DOWN, DOWN_RIGHT}
이제 정수 수학을 사용하여 열거 형을 계산할 수 있습니다 (배열 필요 없음).
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 );
어설 션을 추가하면 중복을 추가했지만 어설 션의 중복은 그렇게 나쁘지 않습니다. 일관성없는 주장이 있으면 곧 알게 될 것입니다. 배포하는 실행 파일을 부 풀리지 않도록 어설 션을 릴리스 버전에서 컴파일 할 수 있습니다. 그럼에도 불구하고이 접근 방식은 코드를 덜보기 흉하게 만드는 것보다 최적화하려는 경우 가장 적합 할 것입니다.
파티에 늦었지만 열거 형 플래그와 범위 검사를 사용하여 깔끔한 작업을 수행 할 수 있습니다.
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;
}
'IT' 카테고리의 다른 글
기간 비교 (0) | 2020.07.28 |
---|---|
사용자가 모든 그룹을 얻는 방법은 무엇입니까? (0) | 2020.07.28 |
점이 선의 오른쪽인지 (0) | 2020.07.28 |
(flag == 0) 또는 (0 == flag) 경우 어느 것이 더 빨리 실행합니까? (0) | 2020.07.28 |
GitHub에 Android Studio 프로젝트를 추가하는 방법 (0) | 2020.07.28 |