stream 优化递归

需求

有一个部门类,如下

public class Department {
    /**
     * 部门 id
     */
    private String id;

    /**
     * 部门名称
     */
    private String name;

    /**
     * 上级部门id, 顶级为 null
     */
    private String parentId;

    ... 省略 get set
}

有一组如下的数据需要进行排序,排序规则为,1级部门后面是所有2级部门,如果2级部门存在下属3级部门,则该2级部门后紧跟着所有3级部门,以此类推。

List<Department> originalList = new ArrayList<>();
originalList.add(new Department("7", "国内采购部", "5"));
originalList.add(new Department("8", "北上广采购部", "7"));
originalList.add(new Department("9", "沿海采购部", "7"));
originalList.add(new Department("2", "国外采购部", "5"));
originalList.add(new Department("10", "欧美采购部", "2"));
originalList.add(new Department("11", "澳洲采购部", "2"));
originalList.add(new Department("3", "国内销售部", "6"));
originalList.add(new Department("4", "国外销售部", "6"));
originalList.add(new Department("1", "运营部", null));
originalList.add(new Department("5", "采购部", "1"));
originalList.add(new Department("6", "销售部", "1"));

排序结果如下


排序结果.png

简单版

实现分析

  • 找到所有一级部门
  • 遍历所有一级部门,找到是否存在二级部门,不存在返回
  • 遍历所有二级部门,查找是否存在三级部门,不存在返回
  • 遍历所有三级部门,查找是否存在四级部门,不存在返回
  • ...
    能看出来这是一个递归算法,尝试用伪代码实现下逻辑
List<Department> 排序结果;

void 查找子部门(上级部门) {
  所有下级部门 = 查找所有下级部门()
  if (所有下级部门不存在) {
    return;
  }
  for (下级部门: 所有下级部门) {
    排序结果.添加(下级部门);
    查找子部门(下级部门);
  }
}

思路清楚了,就是找下级,遍历下级,添加下级到结果集中,同时找下级的所有下级

代码实现

public class DepartmentSort {

    private List<Department> deptList;

    private List<Department> resultList = new ArrayList<>();

    public DepartmentSort(List<Department> deptList) {
        this.deptList = deptList;
    }

    public static List<Department> sort(List<Department> originalList) {
        return new DepartmentSort(originalList).sort();
    }

    private List<Department> sort() {
        findChildren(new Department());
        return resultList;
    }

    /**
     * 查询下级部门
     *
     * @param parentDept
     */
    private void findChildren(Department parentDept) {
        // 查找所有下级部门
        List<Department> childrenList = deptList.stream()
                .filter(d -> Objects.equals(parentDept.getId(), d.getParentId()))
                .collect(Collectors.toList());

        if (childrenList.isEmpty())
            /* 不存在下级部门,跳出递归 */
            return;
        else
            // 遍历所有下级部门,塞入下级部门 resultList 的同时,查找所有该下级部门对应的下级部门
            childrenList.forEach(d -> {
                resultList.add(d);
                findChildren(d);
            });
    }

}

编码测试

List<Department> resultList = DepartmentSort.sort(originalList);

测试结果,达到了预期结果


测试结果1.png

优化版

接下来,分析上个版本代码的不足

  • 上个版本的实现思路是用 一个 resultList 集合去存储所有结果, findChildren 方法是返回 void 的
  • 但其实对于上级部门,调用 findChildren 方法,希望得到的是所有下级部门以及下级部门可能存在的所有更下一级部门的集合

带着这几条思路,改造代码如下

private List<Department> findChildren2(Department parentDept) {
    // 查找所有下级部门
    List<Department> childrenList = deptList.stream()
            .filter(d -> Objects.equals(parentDept.getId(), d.getParentId()))
            .collect(Collectors.toList());

    List<Department> tempList = new ArrayList<>();
    // 遍历所有下级
    childrenList.forEach(d -> {
        tempList.add(d);
        tempList.addAll(findChildren2(d));
    });
    return tempList;
}

经过测试,结果正确,不贴图了...

stream 优化版

继续分析上个版本代码,依然存在不优雅的地方

  • 需要弄一个 tempList 存储所有下级部门以及可能存在的所有更下一级部门
  • 虽然使用了 stream,但是只是用来过滤下级,大材小用了

利用 stream 流的思想去考虑问题,其实遍历出来的下级和可能存在的所有下级部门的所有下级部门,是绑定在一起的,也就是 tempList。根据这样的关系,很容易想到 map 操作符,得到 下级部门.map(下级部门和所有下级部门的下级部门的集合)

带着这个思路,实现如下代码

public class DepartmentSort2 {

    private List<Department> deptList;

    public DepartmentSort2(List<Department> deptList) {
        this.deptList = deptList;
    }

    public static List<Department> sort(List<Department> originalList) {
        return new DepartmentSort2(originalList).sort();
    }

    private List<Department> sort() {
        return findChildren(new Department())
                .collect(Collectors.toList());
    }

    /**
     * 查询下级部门
     *
     * @param parentDept
     */
    private Stream<Department> findChildren(Department parentDept) {
        return deptList.stream()
                .filter(d -> Objects.equals(parentDept.getId(), d.getParentId()))
                .flatMap(d -> Stream.concat(Stream.of(d), findChildren(d)));
    }

}

编写测试代码

List<Department> resultList2 = DepartmentSort2.sort(originalList);

测试结果


stream 版测试结果.png

源码 https://github.com/MoonBottle/sortdemo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,629评论 18 139
  • Kafka设计解析(七)- Kafka Stream 原创文章,转载请务必将下面这段话置于文章开头处。本文转发自技...
    小小少年Boy阅读 5,245评论 0 32
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 01.回娘家,我们都开心得像小鸟 昨天,博爸请了一天假,带着我们一家人回我娘家。 因为生小宝的缘故,我已经有半年没...
    小时光阁楼阅读 731评论 0 1
  • 吃是中国人亘古不变的一个主题,我们每天都在吃,每天都会思考吃什么,古代如此,现代更是喜爱。 吃已经是慢慢地侵蚀每个...
    lin秀阅读 278评论 0 0