基于Table方法降低代码圈复杂度

 描述:

          在项目开发过程中,经常要求圈复杂度不能超过10,有时候写着写着圈复杂度就很大,我在项目代码中见过函数圈复杂度大于100的函数,由于历史的原因,代码越积越多,没人出去重构,导致后面很难懂和维护,所以在编码初期就应该在心中有个要求,就是圈复杂度不能超过10,如果超过10,肯定是代码逻辑写的过于复杂,要回过头来
想想怎么去分解功能,让流程简单易懂。
           本文主要通过一些例子来介绍基于Table方式降低圈复杂度的过程。
      

 例子1:一个简单的游戏控制函数

                 你可能会遇到如下类似的代码:
               
if(strcmpi(command, "north") == 0) {
    if(cur_location->north)
        GoToLocation(cur_location->north);
    else
        Print("Cannot go there");
}
else if(strcmpi(command, "east") == 0) {
    if(cur_location->east)
        GoToLocation(cur_location->east);
    else
        Print("Cannot go there");
}
else if(strcmpi(command, "south") == 0) {
    if(cur_location->south)
        GoToLocation(cur_location->south);
    else
        Print("Cannot go there");
}
else if(strcmpi(command, "west") == 0) {
    if(cur_location->west)
        GoToLocation(cur_location->west);
    else
        Print("Cannot go there");
}

从上面看到该函数的圈复杂度达到了13,包含了很多分支,不容易理解和维护,后续在往里面添加新的特性也容易出错,可以采用如下的方式进行改善。

修改后:
enum SIDE {SIDE_NORTH = 0, SIDE_EAST, SIDE_SOUTH, SIDE_WEST};
struct COMMAND {
   const char * name;
   SIDE side;
};
static const COMMAND commands[] = {
   {"north", SIDE_NORTH},
   {"east", SIDE_EAST},
   {"south", SIDE_SOUTH},
   {"west", SIDE_WEST},
};
for(int i = 0; i < NUM_OF(commands); i++)
    if(strcmpi(commands[i].name, command) == 0) {
        SIDE d = commands[i].side;
        if(cur_location->sides[d])
            GoToLocation(cur_location->sides[d]);
        else
            Print("Cannot go there");
    }
上面整改让函数的圈复杂度为5,变得非常清楚和易维护。

 例子2:计算租一个CD价钱的函数

  
double result = 0;
switch(movieType) {
   case Movie.REGULAR:
     result += 2;
     if(daysRented > 2)
        result += (daysRented - 2) * 1.5;
     break;
 
   case Movie.NEW_RELEASE:
     result += daysRented * 3;
     break;
 
   case Movie.CHILDRENS:
     result += 1.5;
     if(daysRented > 3)
        result += (daysRented - 3) * 1.5;
     break;
}
修改后的版本:
enum MovieType {Regular = 0, NewRelease = 1, Childrens = 2};
 
                             // Regular   NewRelease   Childrens
const double initialCharge[] = {2,             0,        1.5};
const double initialDays[] =   {2,             0,          3};
const double multiplier[] =    {1.5,           3,        1.5};
 
double price = initialCharge[movie_type];
if(daysRented > initialDays[movie_type])
    price += (daysRented - initialDays[movie_type]) * multiplier[movie_type];
其实也可以采用继承的方式来解决上面的许多switch case分支,用一个类来介绍 regular 价格, 另外一个类 new releases价格, 一个类计算 children's movie价格。

 例子3:判断字符和数字


       有时需要判断一个字符是否为数字或者大小写,我们经常会使用如下的方法:
   
int isalnum(int ch) {
    return 'a' <= ch && ch <= 'z' ||
           'A' <= ch && ch <= 'Z' ||
           '0' <= ch && ch <= '9';
}

但是在C的运行库中判断是否为数字和 字母采用了如下的方式,如ctype.h中
static const unsigned char properties[] = {
      0,  0,  0,  0,  0,  0,  0,  0,  0, 16, 16, 16, 16, 16,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
     16,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,
    204,204,204,204,204,204,204,204,204,204,160,160,160,160,160,160,
    160,202,202,202,202,202,202,138,138,138,138,138,138,138,138,138,
    138,138,138,138,138,138,138,138,138,138,138,160,160,160,160,160,
    160,201,201,201,201,201,201,137,137,137,137,137,137,137,137,137,
    137,137,137,137,137,137,137,137,137,137,137,160,160,160,160,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
};
#define islower(ch)  (properties[ch] & 0x01)
#define isupper(ch)  (properties[ch] & 0x02)
#define isdigit(ch)  (properties[ch] & 0x04)
#define isalnum(ch)  (properties[ch] & 0x08)
#define isspace(ch)  (properties[ch] & 0x10)
#define ispunct(ch)  (properties[ch] & 0x20)
#define isxdigit(ch) (properties[ch] & 0x40)
#define isgraph(ch)  (properties[ch] & 0x80)


如果需要存储更少的信息,可以采用位数组,但是需要更多的操作去检索该值,如下:
inline int isalnum(int ch) {
    static const unsigned int alnum[] = {
        0x0, 0x3ff0000, 0x7fffffe, 0x7fffffe, 0x0, 0x0, 0x0, 0x0,
    };
    return (alnum[ch >> 5]) & (1 << (ch & 31));
}
从例子3中可以看出,善于利用数组可以有效减少程序的复杂度,有时候还能提高执行效率,不过在开发过程中还是要以可维护性和可理解性为主,除非是在关键路径上需要考虑性能指标。


### 回答1: 运动规划是指在给定的环境中,规划机器人或其他自主设备的行动路径,从而达到预期目标的过程。JPS算法(Jump Point Search)是一种搜索算法,它可以快速地找到可到达目标的关键节点,并跨越无障碍区域直接到达下一个关键节点,从而提高规划效率。下面介绍如何用Matlab实现JPS算法的代码。 JPS算法的关键是设计合适的启发式函数,以快速地确定距离目标最近的关键节点。在Matlab中,可以通过实现一个距离函数来实现这个功能。具体步骤如下: 1. 定义地图和起点、终点坐标: map = zeros(10,10); % 定义10*10的地图 start = [1,1]; % 定义起点坐标 goal = [10,10]; % 定义终点坐标 2. 定义启发式函数: function [h] = getHeuristic(current, goal, map) % 计算当前节点到目标节点的距离,即启发式函数 dx = abs(current(1) - goal(1)); % x轴距离 dy = abs(current(2) - goal(2)); % y轴距离 h = 1.0 * (dx + dy); % 曼哈顿距离 end 3. 实现JPS算法: function [path] = JPS(start, goal, map) % 实现JPS算法的主函数 % 初始化 gScore = Inf(size(map)); % 起点到节点的距离,默认为无穷大 fScore = Inf(size(map)); % 节点到终点的距离,默认为无穷大 cameFrom = NaN(size(map)); % 记录每个节点是通过哪个节点到达的 openSet = [start]; % 初始时,只有起点在待访问队列中 gScore(start(1),start(2)) = 0; % 起点到起点的距离为0 fScore(start(1),start(2)) = getHeuristic(start, goal, map); % 启发式函数 % 循环查找关键节点 while ~isempty(openSet) % 找到当前fScore值最小的节点,作为当前节点 [~, currentIndex] = min(fScore(openSet(:,1), openSet(:,2))); current = openSet(currentIndex,:); % 如果当前节点就是终点,退出循环 if current == goal path = reconstructPath(cameFrom, goal); return end % 判断当前节点是否有关键节点 jumpPoints = findJumpPoints(current, goal, map); % 对于每一个关键节点,计算其gScore和fScore值 for i = 1:size(jumpPoints, 1) jumpPoint = jumpPoints(i,:); % 计算距离 if map(jumpPoint(1),jumpPoint(2)) ~= 1 % 如果不是障碍物 tentative_gScore = gScore(current(1),current(2)) + distance(current,jumpPoint); if tentative_gScore < gScore(jumpPoint(1),jumpPoint(2)) cameFrom(jumpPoint(1),jumpPoint(2)) = current; gScore(jumpPoint(1),jumpPoint(2)) = tentative_gScore; fScore(jumpPoint(1),jumpPoint(2)) = gScore(jumpPoint(1),jumpPoint(2)) + getHeuristic(jumpPoint, goal, map); if ~ismember(jumpPoint, openSet, 'rows') openSet = [openSet; jumpPoint]; end end end end end % 如果无法找到路径,返回空值 path = []; end 4. 实现辅助函数findJumpPoints和distance: function [jumpPoints] = findJumpPoints(current, goal, map) % 实现findJumpPoints辅助函数 % 预计算相对于当前节点的跳点(关键节点)集合。类似于A * % 请参阅http://users.cecs.anu.edu.au/~dharabor/data/papers/harabor-grastien-aaai11.pdf dx = sign(goal(1) - current(1)); dy = sign(goal(2) - current(2)); [m,n] = size(map); % 如果当前节点是障碍物,直接返回空数组 if map(current(1),current(2)) == 1 jumpPoints = []; return end jumpPoints = []; % 初始化跳点集合 % 如果当前节点是终点或障碍物节点,返回当前节点 if isEndNode(current, goal) || isObstacle(current, map) jumpPoints = [jumpPoints; current]; return end % 在当前方向上前进,以找到关键节点 if dx ~= 0 % 水平方向前进 for i = 1:dx:(n - current(2)) nextNode = current + [0,i]; % 如果下一个节点是障碍物节点,或者终点,找到关键点。然后递归搜索。 if isEndNode(nextNode, goal) || isObstacle(nextNode, map) jumpPoints = [jumpPoints; nextNode]; return end % 找到强制点 if hasForcedNeighbor(nextNode, dy, map) jumpPoints = [jumpPoints; nextNode]; break end end elseif dy ~= 0 % 垂直方向前进 for i = 1:dy:(m - current(1)) nextNode = current + [i,0]; % 如果下一个节点是障碍物节点,或者终点,找到关键点。然后递归搜索。 if isEndNode(nextNode, goal) || isObstacle(nextNode, map) jumpPoints = [jumpPoints; nextNode]; return end % 找到强制点 if hasForcedNeighbor(nextNode, dx, map) jumpPoints = [jumpPoints; nextNode]; break end end else % 当前节点即为终点 return end % 递归搜索找到所有的跳点 for i = 1:size(jumpPoints, 1) jumpPoint = jumpPoints(i,:); neighbors = getNeighbors(jumpPoint, map); newdx = jumpPoint(1) - current(1); newdy = jumpPoint(2) - current(2); % 对于每一个与当前节点相邻的点,找到跳点 for k = 1:size(neighbors, 1) neighbor = neighbors(k,:); % 如果邻居不是当前节点的一个直接邻居,跳过它 if ~(neighbor(1) == jumpPoint(1) && neighbor(2) == jumpPoint(2)) continue end % 如果邻居为障碍物节点,跳过它 if isObstacle(neighbor, map) continue end nextNode = jumpPoint + [newdx,newdy]; jumpPoints = [jumpPoints; findJumpPoints(nextNode, goal, map)]; end end end function [flag] = isEndNode(node, goal) % 实现isEndNode辅助函数 flag = node(1) == goal(1) && node(2) == goal(2); end function [flag] = isObstacle(node, map) % 实现isObstacle辅助函数 flag = map(node(1),node(2)) == 1; end function [flag] = hasForcedNeighbor(node, direction, map) % 实现hasForcedNeighbor辅助函数 [m,n] = size(map); dx = sign(direction); for y = (node(2) + dx):dx:n if (map(node(1),y) == 1) % 如果是障碍物,可以强制下一个节点 break end % 下一步要继续向上或向下移动,不能直接强制前进,需要找到强制节点 if (map(node(1) + direction,y) ~= 1) && (map(node(1) - direction,y) ~= 1) flag = true; return end end flag = false; end function [distance] = distance(node1, node2) % 实现distance辅助函数 dx = node2(1) - node1(1); dy = node2(2) - node1(2); distance = sqrt(dx^2 + dy^2); end function [neighbors] = getNeighbors(node, map) % 实现getNeighbors辅助函数 [m,n] = size(map); dx = [-1, 0, 1, 1, 1, 0,-1,-1]; % 8个方向 dy = [-1,-1,-1, 0, 1, 1, 1, 0]; % 8个方向 neighbors = []; for i = 1:length(dx) x = node(1) + dx(i); y = node(2) + dy(i); % 判断是否越界或是障碍物 if (x >= 1 && x <= m && y >= 1 && y <= n) && (map(x,y) ~= 1) neighbors = [neighbors; x,y]; end end end 5. 实现路径重构函数: function [path] = reconstructPath(cameFrom, current) % 实现路径重构函数 path = current; while ~isnan(cameFrom(current(1),current(2))) current = cameFrom(current(1),current(2)); path = [current; path]; end end 运行上述代码即可得到JPS算法的路径规划结果。需要注意的是,完整的JPS算法还包括很多细节处理和优化,如路径平滑、避免重复计算等。因此,上述代码仅供参考,实际应用中还需要根据具体情况进行改进和优化。 ### 回答2: 运动规划JPS算法是一种高效的路径规划算法,它利用启发式搜索的思想,通过预处理地图信息来降低搜索难度,从而缩短规划时间。下面是基于MATLAB编写的JPS算法代码实现。 首先,需要输入地图信息和起点、终点坐标。然后进行地图信息的预处理,使用跳点搜索方法来寻找合适的跳点,对每个跳点进行跳跃搜索,记录其有效的子节点,然后将其存储在表格中。 接下来,使用二叉堆来实现启发式搜索,利用启发函数来选择最优的节点进行扩展,直到扩展到终点或者达到搜索上限。 最后,将最优路径点输出,并在地图上绘制路径,完成路径规划的过程。 以下是简要代码实现: 1. 处理地图信息和起点、终点坐标: ```matlab % define map info, start point and goal point map = [...]; % input map start = [...]; % input start point goal = [...]; % input goal point ``` 2. 预处理地图信息,并存储跳点表格: ```matlab % preprocess map info and find jump points [jumpPointMap, jumpTable] = preprocessMap(map); ``` 3. 启发式搜索: ```matlab % define open and close lists openList = BinaryHeap(); closeList = containers.Map('KeyType','char','ValueType','any'); % add start node to open list startNode = Node(start, 0, 0, [], 0, 0, 0); openList.insertNode(startNode); % search until reaching goal or exceed max search step while (~openList.isEmpty() && (step < maxStep)) % select node with lowest cost in open list to expand currentNode = openList.popNode(); hash = hashNode(currentNode); % check if current node's hash value is in close list if (closeList.isKey(hash)) continue; end closeList(hash) = currentNode; % check if current node is goal node if (currentNode.isGoal(goal)) % output the optimal path found path = currentNode.getPath(); plotPath(map, path); break; end % expand the current node expandedNodes = expandNode(currentNode, jumpPointMap, jumpTable); % add valid nodes to open list for i = 1:length(expandedNodes) node = expandedNodes(i); hash = hashNode(node); if (~closeList.isKey(hash)) openList.insertNode(node); end end end ``` 4. 最终路径输出和地图绘制: ```matlab function plotPath(map, path) % plot the optimal path on the map figure; imshow(map); hold on; plot(path(:,2), path(:,1), '.-', 'Color', 'r', 'MarkerSize', 20); end ``` 需要注意的是,以上代码只是简要实现,具体还需要根据实际场景进行调整和优化。 ### 回答3: JPS算法(Jump Point Search)是一种高效的路径规划算法,可以用于解决在网格地图中从起点到终点的最短路径问题。JPS算法利用预处理技术,跳过不必要的地图搜索,从而降低了计算复杂度。 运动规划JPS算法的MATLAB代码实现需要进行以下几个步骤: 1.定义网格地图和起点、终点坐标。 例如,可以使用MATLAB中的图像处理工具箱中的bwlabel函数对地图进行二值化,并使用MATLAB中的imshow函数将地图可视化。同时,定义起点和终点坐标。 2.构建跳跃点。 JPS算法中的主要思想是跳跃点。跳跃点是指从当前节点到下一个节点的跳跃位置,即可以直接到达下一个节点而无需搜索途中的节点。因此,需要编写代码来构建跳跃点。 3.搜索最短路径。 使用A*算法或Dijkstra算法等搜索算法,在地图中搜索出起点到终点的最短路径。 4.可视化路径。 将搜索出的最短路径可视化。可以使用MATLAB中的plot函数,将路径上的点经过描点后连接成直线。 以上是关于运动规划JPS算法MATLAB代码实现的基本介绍。在实践中,还需要注意代码实现中的各种细节。此外,为了提高代码的可复用性和可扩展性,建议将代码进行模块化,分解为不同的函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值