【通用行业开发部】记一次流程图有向回环校验

简介: 流程图在工作中经常遇见,,比如单据审批,操作规程等,对于一些业务,如果流程图中出现了回环,那么可能导致工作陷入死循环,因此在制作保存流程图的时候,需要校验流程图中是否有回环问题,最近项目中有一个工业生产的流程图,简记一下回环校验过程。

流程图中的回环

回环是指在流程图中根据流程走向形成闭环,如下图:
流程回环.PNG

处理思路

从起始节点开始,遍历所有路径,判断是否回环,直至结束节点。

处理过程

一.数据结构
{"name":"ceshi","nodeList":[{"id":"id1","name":"开始节点","type":"start"},{"id":"id2","name":"结束节点","type":"end"},{"id":"id3","name":"a","type":"task"},{"id":"id4","name":"b","type":"task"},{"id":"id5","name":"c","type":"task"},{"id":"id6","name":"d","type":"task"},{"id":"id7","name":"e","type":"task"}],"lineList":[{"from":"id1","to":"id3"},{"from":"id3","to":"id4"},{"from":"id4","to":"id5"},{"from":"id5","to":"id6"},{"from":"id6","to":"id2"},{"from":"id5","to":"id7"},{"from":"id7","to":"id4"}]}
二.构建数据模型

  
  private FlowDataParser(Map<String, Node> nodeMap){
      this.nodeMap = nodeMap;
  }

  public static FlowDataParser parse(String jsonString){
      FlowContent flowContent = JSONObject.parseObject(jsonString, FlowContent.class);
      List<Map<String, Object>> nodeList = flowContent.nodeList;
      List<Map<String, Object>> lineList = flowContent.lineList;
      Map<String, Node> nodeMap = new HashMap<String, FlowDataParser.Node>();
      // 创建节点
      for (Map<String, Object> map : nodeList) {
          String id = map.get("id").toString();
          Node node = new Node();
          node.content = map;
          node.id = id;
          node.type = map.get("type").toString();
          nodeMap.put(id, node);
      }
      // 创建链表数据结构
      for (Map<String, Object> map : lineList) {
          String fromId = map.get("from").toString();
          String toId = map.get("to").toString();
          Node fromNode = nodeMap.get(fromId);
          Node toNode = nodeMap.get(toId);
          fromNode.nextNodes.add(toNode);
          toNode.preNodes.add(fromNode);
      }
      return new FlowDataParser(nodeMap);
  }
  
  @Data
  private static class FlowContent{
      private String name;
      private List<Map<String, Object>> nodeList;
      private List<Map<String, Object>> lineList;
  }
  
  
  private static class Node {
      // 节点主键
      private String id;
      // 节点类型
      private String type;
      // 上一级节点
      private List<Node> preNodes = new ArrayList<FlowDataParser.Node>();
      // 下一级节点
      private List<Node> nextNodes = new ArrayList<>();
      // 路径节点
      private Set<String> paths = new HashSet<>();
      // 节点内容
      private Map<String, Object> content;
      
  }

三.遍历所有节点校验是否出现回环

    public boolean cycleCheck(){
        List<Node> nodes = new ArrayList<>();
        nodes.add(this.getStartNode());
        // 从开始节点循环遍历,校验回环
        while(true){
            List<Node> nextNodes = new ArrayList<>();
            for (Node n : nodes) {
                if (ifCycle(n)) {
                    // 出现回环
                    return false;
                } else if (CollectionUtils.isNotEmpty(n.nextNodes)) {
                    // 没有出现回环,获取下级节点,继续遍历
                    nextNodes.addAll(n.nextNodes);
                }
            }
            
            if (nextNodes.size() == 0) {
                // 不存在下级节点,流程结束
                return true;
            } else {
                // 继续遍历
                nodes = nextNodes;
            }
        }
    }
    
    // 查找开始节点
    private Node getStartNode(){
        for(Map.Entry<String, Node> entry : this.nodeMap.entrySet()){
            if ("start".equals(entry.getValue().type)) {
                return entry.getValue();
            }
        }
        return null;
    }
    
    
    private boolean ifCycle(Node node){
        Set<String> paths = node.paths;
        List<Node> nextNodes = node.nextNodes;
        
        if (CollectionUtils.isNotEmpty(nextNodes)) {
            for (Node nextNode : nextNodes) {
                if (paths.contains(nextNode.id)
                        || node.id.equals(nextNode.id)) {
                    // 路径节点中包含下一个节点,出现回环
                    return true;
                } else {
                    // 路径节点中不包含下一级节点,没有出现回环,下级节点收集路径信息
                    nextNode.paths.addAll(node.paths);
                    nextNode.paths.add(node.id);
                }
            }
        }
        return false;
    }

四.跑一下代码,看下结果

        String flowDataStr = "{    \"name\":\"ceshi\",    \"nodeList\":[        {            \"id\":\"id1\",\"name\":\"开始节点\",\"type\":\"start\"        },        {            \"id\":\"id2\",\"name\":\"结束节点\",\"type\":\"end\"        },        {            \"id\":\"id3\",\"name\":\"a\",\"type\":\"task\"        },        {            \"id\":\"id4\",\"name\":\"b\",\"type\":\"task\"        },        {            \"id\":\"id5\",\"name\":\"c\",\"type\":\"task\"        },        {            \"id\":\"id6\",\"name\":\"d\",\"type\":\"task\"        },        {            \"id\":\"id7\",\"name\":\"e\",\"type\":\"task\"        }    ],    \"lineList\":[        {\"from\":\"id1\",\"to\":\"id3\"},        {\"from\":\"id3\",\"to\":\"id4\"},        {\"from\":\"id4\",\"to\":\"id5\"},        {\"from\":\"id5\",\"to\":\"id6\"},        {\"from\":\"id6\",\"to\":\"id2\"},                {\"from\":\"id5\",\"to\":\"id7\"},        {\"from\":\"id7\",\"to\":\"id4\"}    ]}";
        FlowDataParser parser = FlowDataParser.parse(flowDataStr);
        if (parser.cycleCheck()) {
            System.out.println("校验成功");
        } else {
            System.out.println("校验失败,出现回环");
        }
    }

在写遍历的时候原本使用了递归,结果递归次数太多stackoverflow了,改成了循环,小计一下

相关文章
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
32696 78
如何保证分布式文件系统的数据一致性
|
前端开发 容器
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局(上)
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局
17745 19
|
设计模式 存储 监控
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36676 19
设计模式(C++版)
|
存储 编译器 C语言
抽丝剥茧C语言(初阶 下)(下)
抽丝剥茧C语言(初阶 下)
|
机器学习/深度学习 人工智能 自然语言处理
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24756 14
|
机器学习/深度学习 弹性计算 监控
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36658 15
重生之---我测阿里云U1实例(通用算力型)
|
SQL 存储 弹性计算
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29835 52