VAE《放肆》如約而至: 遞歸算法 + Stream函數式編程 + Lambda相遇實現樹狀結構


一、《放肆》如約而至

  今早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串放進去解析一下, 結果如下:

最后附上我嵩歌今日發行的新歌的歌詞結束(我嵩哥就是有才):

”晚秋 乘冷冷西風 施一葦渡江功

履尖 知江湖涼薄 於恍惚浮沉后

孤身獨心 快意領教紅塵三兩課

爾后勿書 勿見 勿擾我清夢

酒旗 飄揚在半坡 饞蟲趨之難鎖

落座 入一壺金波 浣往事穿腸過

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

醉聞其口頻吐蠻言來犯我

你的放肆 燒燃起怒火

我的放肆 如大雨滂沱

冷刀喉前過 手沉疾如風

互以命相搏

你的放肆 掀風起雲涌

我的放肆 平滄海瀾波

皆謂我落拓 誰解我瘋魔

酒醒 竟是你 在關外候我”

  

獐死於麝 鹿死於角
危險和榮譽總是成正比噠
請大家多多批評指教哈


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM