我有以下代码:
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
链;真的很丑。是否有另一种可能更清洁的方式来写这个?
this->_car.getAbsoluteAngle()
,它会看起来不那么难看,打字更少,阅读效果更好。
this
(this->
) 的显式取消引用,并且对可读性没有任何好处。
>
测试,代码会变得不那么难看;它们不是必需的,因为它们中的每一个都已经在前面的 if
语句中进行了测试(以相反的方向)。
else 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;
}
table[angle%360/30]
答案之一,因为它便宜且无分支。 远 比树搜索循环便宜,如果它编译为与源代码相似的 asm。 (std::unordered_map
通常是哈希表,但 std::map
通常是红黑二叉树。接受的答案有效地使用 angle%360 / 30
作为角度的完美哈希函数(在复制了几个条目之后,Bijay 的答案甚至避免有一个偏移量))。
lower_bound
。这将比 map
高效得多。
this->_car.getAbsoluteAngle()
并从每个 OP 的 if()
子句中删除冗余比较来简化(检查已经与前面的 if()) 匹配的东西。或者使用@wilx 的排序数组建议。
创建一个数组,其中的每个元素都与一个 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];
这现在给了我们 30 的 RIGHT
,60 的 UP_RIGHT
,等等。
在 0 的情况下,表达式变为 (-1 % 360) / 30
。这是有效的,因为 -1 % 360 == -1
和 -1 / 30 == 0
,所以我们仍然得到索引 0。
C++ standard 的第 5.6 节确认了这种行为:
4 二元 / 运算符产生商,二元 % 运算符产生第一个表达式除以第二个表达式的余数。如果 / 或 % 的第二个操作数为零,则行为未定义。对于整数操作数, / 运算符产生代数商,其中任何小数部分被丢弃。如果商 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;
该代码并不难看,它简单、实用、可读且易于理解。它将以自己的方式被隔离,因此在日常生活中没有人需要处理它。万一有人必须检查它——也许是因为他正在调试你的应用程序以解决其他地方的问题——这很容易,他需要两秒钟才能理解代码及其作用。
如果我正在做这样的调试,我很高兴不必花五分钟时间来了解你的函数的作用。在这方面,所有其他功能完全失败,因为它们改变了一个简单的、忘记它、没有错误的例程,陷入复杂的混乱状态,人们在调试时将被迫深入分析和测试。作为我自己的项目经理,如果开发人员执行一项简单的任务,而不是以一种简单、无害的方式实现它,而是浪费时间以一种过于复杂的方式实现它,我会感到非常沮丧。想想你一直在浪费时间思考它,然后来问,一切只是为了恶化事物的维护和可读性。
也就是说,您的代码中存在一个常见错误,使其可读性降低,并且您可以很容易地进行一些改进:
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;
将它放入一个方法中,将返回值分配给对象,折叠该方法,然后永远忘记它。
PS还有一个超过330阈值的bug,但是不知道你想怎么处理,所以根本没修。
稍后更新
根据评论,您甚至可以完全摆脱 else if:
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
,不如遍历您的各种可能性:
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
处理异常而不是 return
处理 NONE
是另一种选择)。
然后你会打电话给:
_car.edir = direction_from_angle(_car.getAbsoluteAngle(), {
{30, 60, EDirection::UP_RIGHT},
{60, 120, EDirection::UP},
// ... etc.
});
这种技术称为 data-driven programming。除了摆脱一堆 if
之外,它还允许您轻松地使用添加更多方向(例如,NNW)或减少数量(左、右、上、下),而无需重新编写其他代码。
(处理您的第一个特殊情况留作“读者练习”。:-))
if(angle <= angleRange.max)
,但对于使用 enum class
等 C++11 功能时 +1。
尽管基于 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;
}
不定期副业成功案例分享
q = a/b
和r = a%b
则q * b + r
必须等于a
。因此,在 C99 中,余数为负是合法的。 BorgLeader,你可以用(((angle % 360) + 360) % 360) / 30
解决问题。GetDirectionForAngle
是我提议作为 if/else 级联的替代品,它们都是 O(1)...