菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
55
0

VAE《放肆》如约而至: 递归算法 + Stream函数式编程 + Lambda相遇实现树状结构

原创
05/13 14:22
阅读数 57686

一、《放肆》如约而至

  今早5:00在迷迷糊糊中醒来,打开手机一看,许嵩又发新歌了,名字叫做《放肆》,澎湃的旋律,依旧古典高雅的用词,这个大男孩,已经不像12年那时候发些伤感非主流的歌曲了,86年出生的他,除了依旧留着亘古不变的长刘海发型,心理俨然住进了一个老灵魂。

  再小酣一会儿,6:40闹铃响起,穿衣洗刷梳头,便匆匆忙忙的骑着小单车,赶去了地铁站,重复着之前每天的境况。地铁站人挤人,稀薄的空气被每个人尽力吮吸着,每个人在微不足道的间隙里腾出手来,拿着手机忙活着自己的事,人与人的物理距离看起来很近,但每个人仿佛都被丢进了一个叫做屏幕世界的地方,心理距离远的不着边际。我望着一个个陌生的面孔,放弃了看电子书的想法,带上了耳机微闭双眼,开始循环播放起了音乐,虽然年纪大了,但口味依旧还是许嵩,汪苏泷,薛之谦,毛不易等人的歌曲,俩字:好听。

  有时我就在想,每天重复着差不多的日子究竟有什么意义,我会不会也变成那种25岁就死了,只是75岁才埋的人生,但,这不是我想要的人生。听着汪苏泷翻唱的《我们》,我想到了一句很煽情很非主流的话:这么些年我用泡沫堆砌起来的感情,虽然时常修缮小心呵护,可是风一吹,还是散了。

。。。。。。

  来到公司,打卡,开电脑,简单的吃个早饭,心想着今天做点什么或学点什么新知识,10月份已然过去了三分之一还多,可是自己还没构思好要去写点什么,无聊的翻看着自己随意写的一些代码,麻木的点击着公司项目上的功能,我看到了一棵树,好吧,还真是一棵树,会分叉还会横着长的一棵树,想了想,好久没有专注于代码本身了,那就写点实现某项功能的小例子,比如:使用递归结合Lambda表达式去实现这棵树。在这里,我想提供一个类似于思想模板的代码,基本使用它都能够轻松完成后台的树结构数据的实现,言归正传,开始吧。

 


 二、递归和stream(),Lambda在十月相遇 = 树状数据结构

1.问题来源

先看一张图:

  我们做的这块是一个关于信票的功能,大体是一张信票我签发给你,你再签发给别人,别人再签发给另一批人,另一批人也对外签发,只要手持信票余额足够,是可以签发给多人的,并且也是有可能A->B,B->C,C->A,至于具体的业务我就不做多描述(泄露不好不好),总得来说,我们需要用用一张图来描绘出这种层级关系,明显这是一种典型的树状结构(由于我们的每条数据有自己代号,所以刚刚我举的ACBA并不会形成一个闭环,那两个A所代表的层级也是不同的),这时候需要我们处理成树状架构的数据组给前台了。我展示的这张图层级只到了2,而且还没展示很复杂的结构(造条数据很麻烦的,原谅我的懒惰),但大体意思应该能看明白。

  一看,有觉悟的后台同志们就明白了要用递归完成数据处理,我微微一笑,没错,肯定要用递归,然并软,我的内心早已波涛汹涌:我的递归玩的是真不溜,工作到现在,还真没正儿八经的用过递归思维。不过我不用太过担心,毕竟最初始的这个任务不是落我头上,不过这块的业务我也参与了不少,所以我也得想想。同事去研究了,因为我们的这个数据不是直来直去的那种,并不是在表中存了父子id可以直接关联取数据,所以处理起来也没那么容易,后来差不多同事忙活了不少时间,写了一套代码,可是数据量一多的时候,貌似树状数据还会出问题,这就不是我能左右的了。然而,后来我写的一块功能也用到了展示这个树状图,后来参考了下网上处理这种问题的代码,我做了下处理和变形,完美契合,虽然代码多了点,但屡试不爽,下面是我处理后的代码:

package cn.exrick.xboot.modules.bill.entity;
import com.alibaba.fastjson.JSON;
import java.util.*;
/**
 * zae
*/
public class BillNodeTree {
public static Map getTreeList(List<BillCirculation> billCirculationList) {
    // 读取层次数据结果集列表
    List dataList = VirtualDataGenerator.getVirtualResult(billCirculationList);
    if(dataList==null || dataList.size()==0){
        return new HashMap();
    }
    // 节点列表(散列表,用于临时存储节点对象)
    HashMap nodeList = new HashMap();
    // 根节点
    Node root = null;
    // 根据结果集构造节点列表(存入散列表)
    for (Iterator it = dataList.iterator(); it.hasNext();) {
        Map dataRecord = (Map) it.next();
        Node node = new Node();
        node.id = (String)dataRecord.get("id");
        node.orgIssueUnitUnnoName = (String) dataRecord.get("orgIssueUnitUnnoName");
        node.level = Integer.parseInt(dataRecord.get("level")+"");
        node.parentId = (String)dataRecord.get("parentId");
        nodeList.put(node.id, node);
    }
    // 构造无序的多叉树
    Set entrySet = nodeList.entrySet();
    for (Iterator it = entrySet.iterator(); it.hasNext();) {
        Node node = (Node) ((Map.Entry) it.next()).getValue();
        if (node.parentId == null || node.parentId.equals("") || "null".equals(node.parentId)) {
            root = node;
        } else {
            ((Node) nodeList.get(node.parentId)).addChild(node);
        }
    }
    // 输出无序的树形菜单的JSON字符串
   // System.out.println(root.toString());
    // 对多叉树进行横向排序
   // root.sortChildren();
    // 输出有序的树形菜单的JSON字符串
    Map operatorMaps = (Map) JSON.parseObject(root.toString(),Map.class);
    return  operatorMaps;
}
}

/**
 * 节点类
*/
class Node {
/**
 * 节点编号
 */
public String id;
/**
 * 节点内容
 */
public String orgIssueUnitUnnoName;
/**
 * 级别
 */
public Integer level;
/**
 * 父节点编号
 */
public String parentId;
/**
 * 孩子节点列表
 */
private Children children = new Children();

// 先序遍历,拼接JSON字符串
public String toString() {
    String result = "{"
            + "id : '" + id + "'"
            + ", orgIssueUnitUnnoName : '" + orgIssueUnitUnnoName + "'"
    + ", level : " + level + "";

    if (children != null && children.getSize() != 0) {
        result += ", children : " + children.toString();
    } else {
        result += ", expand : false";
    }

    return result + "}";
}

// 兄弟节点横向排序
public void sortChildren() {
    if (children != null && children.getSize() != 0) {
        children.sortChildren();
    }
}

// 添加孩子节点
public void addChild(Node node) {
    this.children.addChild(node);
}
}

/**
* @author zangchuanlei
* 孩子列表类
*/
class Children {
private List list = new ArrayList();

public int getSize() {
    return list.size();
}

public void addChild(Node node) {
    list.add(node);
}

// 拼接孩子节点的JSON字符串
public String toString() {
    String result = "[";
    for (Iterator it = list.iterator(); it.hasNext();) {
        result += ((Node) it.next()).toString();
        result += ",";
    }
    result = result.substring(0, result.length() - 1);
    result += "]";
    return result;
}

// 孩子节点排序
public void sortChildren() {
    // 对本层节点进行排序
    // 可根据不同的排序属性,传入不同的比较器,这里传入ID比较器
    Collections.sort(list, new NodeIDComparator());
    // 对每个节点的下一层节点进行排序
    for (Iterator it = list.iterator(); it.hasNext();) {
        ((Node) it.next()).sortChildren();
    }
}
}

/**
 * @author zangchuanlei
 * 节点比较器
 */
class NodeIDComparator implements Comparator {
    // 按照节点编号比较
    public int compare(Object o1, Object o2) {
        int j1 = Integer.parseInt(((Node)o1).id);
        int j2 = Integer.parseInt(((Node)o2).id);
        return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1));
    }
}

/**
 * @author zangchuanlei
 * 构造虚拟的层次数据
 */
class VirtualDataGenerator {
    // 构造无序的结果集列表,实际应用中,该数据应该从数据库中查询获得;
    public static List getVirtualResult(List<BillCirculation> billCirculationList) {
        List dataList = new ArrayList();
        for(BillCirculation billCirculation:billCirculationList){
            HashMap dataRecord1 = new HashMap();
            dataRecord1.put("id", billCirculation.getId());
            dataRecord1.put("orgIssueUnitUnnoName",billCirculation.getCorpName());
            dataRecord1.put("level",billCirculation.getLevel());
            if(billCirculation.getLevel() == 1){
                dataRecord1.put("parentId", "");
            }else{
                String parentId = null;
                for(BillCirculation billCirculation1:billCirculationList){
                    if(billCirculation.getIssueUnitUnno().equals(billCirculation1.getSignInUnitUnno())
                    && billCirculation1.getLevel() == billCirculation.getLevel()-1){
                        parentId = billCirculation1.getId();
                    }
                }
                dataRecord1.put("parentId",parentId);
            }
            dataList.add(dataRecord1);
        }
        return dataList;
    }
}

  坦白说,虽然我贴出了这套自己改编后的代码,但是却不是我今天讲的重点,毕竟核心思想是人家的,我只是将它和自己的要写的功能完美契合了,而且复用性不强,它用了些比较器进行处理,代码也太长太杂,和我今天的主题递归+Lambda不太符合,所以看看就行了,别太认真,重点在下面,这一块的内容只是背景而已,并且,为了通俗的讲重点,我会使用最最最简单的例子让大家看明白这个递归思维,所以如果觉得太基础太陋可以消息立正向右转不送。

2.重点内容

  首先先了解下递归算法的概念:程序调用自身的编程技巧称为递归( recursion)。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回

接下来通过一些代码demo来清晰的展示这个过程:

2.1创建实体Tree

import java.util.List;
public class Tree {
private Integer id;//id(唯一标识)
private String number;//编号
private String name;//名称
private Integer parentId;//父节点的ID代号
private Integer myId;//我的ID代号
//正式连接数据库的项目中,此为添加的属性,不是数据库映射字段
private List<Tree> children;

public Tree() {}
public Tree(Integer id, String number, String name, Integer parentId, Integer myId, List<Tree> children) {
    this.id = id;
    this.number = number;
    this.name = name;
    this.parentId = parentId;
    this.myId = myId;
    this.children = children;
}

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}

public String getNumber() {
    return number;
}

public void setNumber(String number) {
    this.number = number;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Integer getParentId() {
    return parentId;
}

public void setParentId(Integer parentId) {
    this.parentId = parentId;
}

public Integer getMyId() {
    return myId;
}

public void setMyId(Integer myId) {
    this.myId = myId;
}

public List<Tree> getChildren() {
    return children;
}

public void setChildren(List<Tree> children) {
    this.children = children;
}
}

2.2 核心代码


import com.alibaba.fastjson.JSON;
import com.zaevn.entity.Tree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LambdaRecursion {
public static void main(String[] args) {
    //获取所有待处理的数据
    List<Tree> treeList = getAllTree();
    List<Tree> collect = treeList.stream()
            //筛选出所有的一级数据(父树不存在)
            .filter(tree -> tree.getParentId() == 0)
            //为一级节点数据设置孩子(调用写的获取子树的递归方法)
            .map(tree -> {
                tree.setChildren(getChrildren(tree, treeList));
                return tree;
            }).collect(Collectors.toList());
    //输出一下处理好的数据
    System.out.println(JSON.toJSONString(collect));
}
/**
 * 递归获取子树
 * @param root 父树实体
 * @param all  全部数据
 * @return
 */
private static List<Tree> getChrildren(Tree root,List<Tree> all){
    List<Tree> treeList = all.stream()
            //筛选出源节点的子节点
            .filter(tree -> tree.getParentId() == root.getMyId())
            //给当前节点设置子树,调用自身获取子树的递归方法
            .map(tree -> {
                tree.setChildren(getChrildren(tree, all));
                return tree;
            }).collect(Collectors.toList());
    return treeList;
}
/**
 * 数据准备类
 * (实际开发中不存在此方法,应该在数据库中获取数据)
 * @return
 */
private static List<Tree> getAllTree(){
    Tree one = new Tree(1001,"1001","皇帝",0,1,new ArrayList<>());
    Tree two1 = new Tree(1002,"1002","耶稣",1,2,new ArrayList<>());
    Tree two2 = new Tree(1003,"1003","鬼影",1,3,new ArrayList<>());
    Tree three1 = new Tree(1004,"1004","军师",2,4,new ArrayList<>());
    Tree three2 = new Tree(1005,"1005","野兽",2,5,new ArrayList<>());
    Tree three3 = new Tree(1006,"1006","伯爵",3,6,new ArrayList<>());
    Tree three4 = new Tree(1007,"1007","牧师",3,7,new ArrayList<>());
    //实际应用中需要在数据库中获取全部的数据
    List<Tree> treeList = Arrays.asList(one,two1,two2,three1,three2,three3,three4);
    return treeList;
}
}

  其实真正的核心代码只有那个main函数以及递归获取子树的静态方法,是不是很简短很简单就能轻松的实现树状数据的处理,之所以代码可以缩减到那么一丁点,主要得益于java stream的函数式编程结合lambda表达式的简短代码,调用filter函数直接过滤出我们想要的数据,然后调用map函数对管道流中某个或某些元素进行处理,最后调用collect函数toList,将管道流转换为List返回,当然,需要对返回的数据进行排序的话还可以使用sort函数进行排序

  大家注意要完成这种数据结构的处理时,首先需要将源节点(最高级父节点)的数据拿出来,也就是我们要的数据的最外层的数据,其次,想办法往里面填充子节点,这里建议的是在实体中添加一个字段,用来存放众多的子节点数据,在获取子节点时,就需要写一个我们今天的主角递归方法了,通过设定过滤规则,找出共性,不断调用自身,就可以完美的将孩子节点的数据装入每一个节点中,直到再也找不到数据结束

  核心代码很短,也加了注释,万变不离其宗,大家遇到此类需要写树状数据的任务时,别慌,基本参考下我这个小例子,就能很快的写出来,也不用向我之前一样,在网上找了一个这么冗长的代码,虽然改造成功了,但是核心思想却没有透彻理解,还是得多练,通过这个小demo,是不是发现,其实递归也没有那么难。

  好了,差点忘了运行下看下结果:

[{"children":[{"children":[{"children":[],"id":1004,"myId":4,"name":"军师","number":"1004","parentId":2},{"children":[],"id":1005,"myId":5,"name":"野兽","number":"1005","parentId":2}],"id":1002,"myId":2,"name":"耶稣","number":"1002","parentId":1},{"children":[{"children":[],"id":1006,"myId":6,"name":"伯爵","number":"1006","parentId":3},{"children":[],"id":1007,"myId":7,"name":"牧师","number":"1007","parentId":3}],"id":1003,"myId":3,"name":"鬼影","number":"1003","parentId":1}],"id":1001,"myId":1,"name":"皇帝","number":"1001","parentId":0}]

 

  (观众一脸黑线,这是什么鬼!)我是在控制台上输出的json串,所以我们可以登录下https://www.bejson.com/将这个json串放进去解析一下, 结果如下:

最后附上我嵩歌今日发行的新歌的歌词结束(我嵩哥就是有才):

”晚秋 乘冷冷西风 施一苇渡江功

履尖 知江湖凉薄 于恍惚浮沉后

孤身独心 快意领教红尘三两课

尔后勿书 勿见 勿扰我清梦

酒旗 飘扬在半坡 馋虫趋之难锁

落座 入一壶金波 浣往事穿肠过

邻桌有客 形色疏狂 貂裘被胡霜

醉闻其口频吐蛮言来犯我

你的放肆 烧燃起怒火

我的放肆 如大雨滂沱

冷刀喉前过 手沉疾如风

互以命相搏

你的放肆 掀风起云涌

我的放肆 平沧海澜波

皆谓我落拓 谁解我疯魔

酒醒 竟是你 在关外候我”

  

獐死于麝 鹿死于角
危险和荣誉总是成正比哒
请大家多多批评指教哈

发表评论

0/200
55 点赞
0 评论
收藏