最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實現(xiàn)平鋪列表(List)互轉(zhuǎn)樹形(Tree)結(jié)構(gòu)

 更新時間:2022年08月05日 09:48:29   作者:青Cheng序員石頭的個人資料頭像 青Cheng序員石頭  
本文主要介紹了Java實現(xiàn)平鋪列表(List)互轉(zhuǎn)樹形(Tree)結(jié)構(gòu),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

很多時候為滿足前后端交互的數(shù)據(jù)結(jié)構(gòu)需求,往往我們需要把平鋪的List數(shù)據(jù)與Tree型層級數(shù)據(jù)結(jié)構(gòu)進行互轉(zhuǎn),這篇文章提供詳實的遞歸和非遞歸的方式去實現(xiàn)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換,為了使用到lambda的特性,Java version >=8。

需求

我們從基礎(chǔ)設(shè)施層獲取了一個列表數(shù)據(jù),列表其中的對象結(jié)構(gòu)如下,注意約束條件如果沒有pid,默認為null。

@Getter
@Setter
@ToString
@Builder
public class NodeEntity {

    /**
     * id
     */
    private Long id;

    /**
     * 父id
     */
    private Long pid;
}

現(xiàn)在我們要將List<NodeEntity> 數(shù)據(jù),按照屬性pid進行Tree型層級封裝,并且支持多層級封裝。一般很容易想到遞歸的實現(xiàn)方法,接下來這篇文章使用一套通用的解決辦法,非遞歸實現(xiàn)結(jié)構(gòu)轉(zhuǎn)換。

實踐List to Tree

遞歸實現(xiàn)

首先定義通用的Tree形數(shù)據(jù)接口。

public interface INodeDTO {

    /**
     * id
     * @return id
     */
    public Long getId();

    /**
     * pid
     * @return  pid
     */
    public Long getPid();


    /**
     * 獲取Children
     * @return  Children
     */
    public List<INodeDTO> getChildren();

    /**
     * 設(shè)置children
     * @param children  children
     */
    public void setChildren(List<INodeDTO> children);

}

每個方法接口有詳細的注釋,無需多說。然后提供通用的轉(zhuǎn)換Function。

/**
     * 非遞歸實現(xiàn)平鋪數(shù)據(jù)轉(zhuǎn)成Tree型結(jié)構(gòu)
     */
    static final Function<List<INodeDTO>,List<INodeDTO>> MULTI_TREE_CONVERTER = sources->
        sources.stream()
            .filter(item->{
                item.setChildren(
                    sources.stream()
                        .filter(e-> Objects.equals(e.getPid(), item.getId()))
                        .collect(Collectors.toList()));
                return item.getPid() == null;})
            .collect(Collectors.toList());

我們利用對象引用,淺拷貝的原理,通過循環(huán)查找來組裝層級,最后根據(jù)pid==null的數(shù)據(jù)一定是Tree型第一層的數(shù)據(jù)的條件進行過濾,篩選出第一層的數(shù)據(jù)組合成新的列表,達到目的。

非遞歸實現(xiàn)

//Establish tree structure
static List<INodeDTO> buildTree (List<INodeDTO>sources){
    List<INodeDTO> results = new ArrayList<>();
    //get root nodes
    List<INodeDTO> rootNodes = sources.stream().filter(x->x.getPid() == null).collect(Collectors.toList());
    for (INodeDTO rootNode : rootNodes) {
        results.add(buildChildTree(sources,rootNode));
    }
    return results;
}

//Recursion, building subtree structure
static INodeDTO buildChildTree(List<INodeDTO>sources,INodeDTO pNode){
    List<INodeDTO> children = new ArrayList<>();
    for (INodeDTO source : sources) {
        if(source.getPid()!=null && source.getPid().equals(pNode.getId())){
            children.add(buildChildTree(sources,source));
        }
    }
    pNode.setChildren(children);
    return pNode;
}

遞歸的實現(xiàn)先獲取所有根節(jié)點,方法builTree總結(jié)根節(jié)點來創(chuàng)建一個樹結(jié)構(gòu),buildChilTree為節(jié)點構(gòu)建一個輔助樹,并拼接當前樹,遞歸調(diào)用buildChilTree來不斷打開當前樹的分支和葉子,直到?jīng)]有找到新的子樹, 完成遞歸,得到樹結(jié)構(gòu)。

遞歸最大的問題可能堆棧太深,容易造成溢出,使用需要謹慎,而且從代碼簡潔度來說,肯定是使用了非遞歸的方式更好。

遞歸代碼還能進一步優(yōu)化,比如改成尾遞歸的方式,有興趣的小伙伴可以嘗試一下。

實例

實例只測試非遞歸實現(xiàn)方法。

那具體怎么使用呢?首先我們通過implements接口INodeDTO,實現(xiàn)我們自己的業(yè)務DTO

@Getter
@Setter
@ToString
@Builder
public class NodeDTO implements INodeDTO {

    private Long id;

    private Long pid;

    List<INodeDTO> children;
}

然后在我們Service層組裝業(yè)務邏輯,這里提供一個listBy的條件查詢接口,從基礎(chǔ)設(shè)施層按照條件撈出List<NodeEntity>,期望轉(zhuǎn)成內(nèi)部包含層級關(guān)系的List<INodeDTO>。

public class UseCase {


    public List<INodeDTO> listBy(String ... condtions){
        System.out.println(Arrays.stream(condtions).reduce((a, b) -> a + ";" + b).orElse(""));
        //TODO get NodeEntities from database
        List<NodeEntity> entities = Arrays.asList(
                NodeEntity.builder().id(1L).pid(null).build(),
                NodeEntity.builder().id(2L).pid(1L).build(),
                NodeEntity.builder().id(3L).pid(1L).build(),
                NodeEntity.builder().id(4L).pid(3L).build()
            );
        List<INodeDTO> sources = entities.stream()
            .map(Factory.NODE_DTO_BUILDER::apply)
            .collect(Collectors.toList());
        return INodeDTO.MULTI_TREE_CONVERTER.apply(sources);
    }

}

提供一個main方法進行測試。

public static void main(String[] args) throws JsonProcessingException {
        UseCase useCase = new UseCase();
        List<INodeDTO> results = useCase.listBy("condtion1", "condtion2");
        //convert json with style
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(results);
        System.out.println(json);
    }

運行后輸出結(jié)果如下,經(jīng)人工肉眼檢驗,達到Tree型層級結(jié)構(gòu)。

實踐Tree to List

上面講到了平鋪列表(List)轉(zhuǎn)樹形(Tree)結(jié)構(gòu),一般來說對于足夠后端數(shù)據(jù)轉(zhuǎn)成前端想要的結(jié)構(gòu)了。但都支持了正向轉(zhuǎn)換,那么反向轉(zhuǎn)換,即樹形(Tree)結(jié)構(gòu)如何轉(zhuǎn)平鋪列表(List)呢?

遞歸實現(xiàn)

遞歸實現(xiàn),分為兩個函數(shù),List<INodeDTO> flatten(List<INodeDTO> flatList) 接受外部調(diào)用,傳入待轉(zhuǎn)換的Tree形結(jié)構(gòu)。第一步便是收集所有的根節(jié)點,然后將所有的根節(jié)點傳入到遞歸函數(shù)List<INodeDTO> flatten(INodeDTO node, List<INodeDTO> flatList中深度遍歷,最后匯總再使用distinct做去重處理得到最終的list結(jié)構(gòu)。

/**
 * Flatten a Tree to a list using recursion(遞歸實現(xiàn))
 * @param flatList flatList
 * @return list
 */
 static List<INodeDTO> flatten(List<INodeDTO> flatList){
    return flatList.stream()
        .filter(x -> x.getPid() == null)
        .collect(Collectors.toList())
        .stream()
        .map(x->{return flatten(x,flatList);})
        .flatMap(Collection::stream)
        .distinct()
        .collect(Collectors.toList());
}

/**
 *  recursion
 * @param node  root node
 * @param flatList  flatList
 * @return  list
 */
static List<INodeDTO> flatten(INodeDTO node,  List<INodeDTO> flatList) {
    List<INodeDTO> results = new ArrayList<>();
    if(node != null){
        // get rid of children & parent references
        INodeDTO n = NodeDTO.builder()
            .pid(node.getPid())
            .id(node.getId())
            .build();
        results.add(n);
    }

    List<INodeDTO> children = node.getChildren();
    for (INodeDTO child : children) {
        if(child.getChildren() != null) {
            // Recursive call - Keep flattening until no more children
            List<INodeDTO> flatten = flatten(child, flatList);
            results.addAll(flatten);
        }
    }
    // stop or exit condition
    return results;
}

非遞歸實現(xiàn)

在非遞歸,即循環(huán)的實現(xiàn)中,我們要用到dequeue數(shù)據(jù)結(jié)構(gòu)。

deque表示一個雙端隊列,這意味著可以從隊列的兩端添加和刪除元素。 deque的不同之處在于添加和刪除條目的不受限制的特性。

在實現(xiàn)中,ArrayDeque將被用作LIFO(即后進先出)數(shù)據(jù)結(jié)構(gòu)(即堆棧)。

/**
 * Flatten a Tree to a list using a while Loop instead of recursion
 * @param flatList   flatList
 * @return list
 */
static List<INodeDTO> flatten2(List<INodeDTO> flatList){
    return flatList.stream()
        .filter(x -> x.getPid() == null)
        .collect(Collectors.toList())
        .stream()
        .map(TreeToMapUtils::flatten2)
        .flatMap(Collection::stream)
        .distinct()
        .collect(Collectors.toList());
}


/**
 * . Flatten using a Deque - Double ended Queue
 *
 **/
 static List<INodeDTO> flatten2(INodeDTO node) {

    if (node == null) {
        return null;
    }

    List<INodeDTO> flatList = new ArrayList<>();
    Deque<INodeDTO> q = new ArrayDeque<>();
     //add the root
    q.addLast(node);
    //Keep looping until all nodes are traversed
    while (!q.isEmpty()) {
        INodeDTO n = q.removeLast();
        flatList.add(NodeDTO.builder().id(n.getId()).pid(n.getPid()).build());
        List<INodeDTO> children = n.getChildren();
        if (children != null) {
            for (INodeDTO child : children) {
                q.addLast(child);
            }
        }
    }
    return flatList;
}

實例

在實例中,我們主要用到list to map 中的輸出,看是否能用flatten函數(shù)還原結(jié)構(gòu)。

public static void main(String[] args) throws JsonProcessingException {
    UseCase useCase = new UseCase();
    List<INodeDTO> results = useCase.listBy("condtion1", "condtion2");
    //convert json with style1 = {NodeDTO@1502} "NodeDTO(id=1, pid=null, children=null)"
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(results);
    System.out.println(json);
    //flatten now
    List<INodeDTO> flatten = TreeToMapUtils.flatten2(results);
    System.out.println(flatten);

}

輸出結(jié)果不但包含Tree形數(shù)據(jù)結(jié)構(gòu),還獲取到了list數(shù)據(jù),如下圖所示,至此,達到效果。

總結(jié)

至此,遞歸和非遞歸分別實現(xiàn)list to tree tree to list已完成,實現(xiàn)比較倉促,有很多細節(jié)處未處理好,希望看到的小伙伴及時指出,不勝感激。

到此這篇關(guān)于Java實現(xiàn)平鋪列表(List)互轉(zhuǎn)樹形(Tree)結(jié)構(gòu)的文章就介紹到這了,更多相關(guān)Java List轉(zhuǎn)樹形Tree結(jié)構(gòu)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring?Cloud?Gateway集成Sentinel流控詳情

    Spring?Cloud?Gateway集成Sentinel流控詳情

    這篇文章主要介紹了Spring?Cloud?Gateway集成Sentinel流控詳情,Sentinel支持對Spring?Cloud?Gateway、Zuul等主流的API?Gateway進行限流,需要的朋友可以參考一下
    2022-09-09
  • SpringBoot返回long,前端接收進度丟失,@JsonSerialize不生效問題

    SpringBoot返回long,前端接收進度丟失,@JsonSerialize不生效問題

    這篇文章主要介紹了SpringBoot返回long,前端接收進度丟失,@JsonSerialize不生效問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • MyBatis詳細執(zhí)行流程的全紀錄

    MyBatis詳細執(zhí)行流程的全紀錄

    這篇文章主要給大家介紹了關(guān)于MyBatis詳細執(zhí)行流程的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • Spring之Scope注解使用詳解

    Spring之Scope注解使用詳解

    spring的bean管理中,每個bean都有對應的scope。在BeanDefinition中就已經(jīng)指定scope,默認的RootBeanDefinition的scope是prototype類型,使用@ComponentScan掃描出的BeanDefinition會指定是singleton,最常使用的也是singleton
    2023-02-02
  • 如何用JAVA判斷當前時間是否為節(jié)假日、周末、工作日及調(diào)休日(不報錯:IOException!)

    如何用JAVA判斷當前時間是否為節(jié)假日、周末、工作日及調(diào)休日(不報錯:IOException!)

    最近公司有個業(yè)務需要判斷工作日,但是每年的節(jié)假日不一樣,下面這篇文章主要給大家介紹了關(guān)于如何用JAVA判斷當前時間是否為節(jié)假日、周末、工作日及調(diào)休日的相關(guān)資料,且不報錯:IOException!,需要的朋友可以參考下
    2023-12-12
  • Java語言實現(xiàn)簡單FTP軟件 FTP軟件效果圖預覽之下載功能(2)

    Java語言實現(xiàn)簡單FTP軟件 FTP軟件效果圖預覽之下載功能(2)

    這篇文章主要為大家詳細介紹了Java語言實現(xiàn)簡單FTP軟件,F(xiàn)TP軟件效果圖預覽之下載功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • Java中的Vector和Stack底層源碼分析

    Java中的Vector和Stack底層源碼分析

    這篇文章主要介紹了Java中的Vector和Stack底層源碼分析,Stack繼承了Vector,Vector底層還是一個List,也就是基于數(shù)組來實現(xiàn)的,所以ArrayList有的優(yōu)點,比如獲取元素的速度快,隨機讀,它都有,需要的朋友可以參考下
    2023-12-12
  • Spring中字段格式化的使用小結(jié)

    Spring中字段格式化的使用小結(jié)

    Spring提供的一個core.convert包?是一個通用類型轉(zhuǎn)換系統(tǒng)。它提供了統(tǒng)一的?ConversionService??API和強類型的Converter SPI,用于實現(xiàn)從一種類型到另一種類型的轉(zhuǎn)換邏輯,這篇文章主要介紹了Spring中字段格式化的使用詳解,需要的朋友可以參考下
    2022-06-06
  • Java實現(xiàn)導出合并Excel單元格

    Java實現(xiàn)導出合并Excel單元格

    隨著數(shù)據(jù)的不斷增長,很多時候需要將數(shù)據(jù)導出到Excel中進行分析、處理和展示。本文將介紹如何使用Java實現(xiàn)Excel導出,并且可以合并單元格,需要的可以參考一下
    2023-04-04
  • 詳解servlet配置load-on-startup的作用

    詳解servlet配置load-on-startup的作用

    本文對load-on-startup的相關(guān)內(nèi)容作了詳細介紹,然后通過具體實例向大家展示了其作用,希望可以給大家一個參考。
    2017-09-09

最新評論

甘泉县| 莱阳市| 威海市| 深水埗区| 英超| 民勤县| 巴林右旗| 门源| 临潭县| 普宁市| 攀枝花市| 名山县| 古丈县| 石嘴山市| 札达县| 页游| 柳林县| 广河县| 永城市| 富宁县| 福安市| 延长县| 鲁山县| 建平县| 山丹县| 南陵县| 巴彦县| 康定县| 彩票| 平舆县| 古蔺县| 河西区| 崇仁县| 洛南县| 焦作市| 洪洞县| 吉安县| 桃源县| 贡觉县| 邹城市| 阜平县|