<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
很多時候為滿足前後端互動的資料結構需求,往往我們需要把平鋪的
List
資料與Tree
型層級資料結構進行互轉,這篇文章提供詳實的遞迴和非遞迴的方式去實現資料結構轉換,為了使用到lambda
的特性,Java version >=8
。
我們從基礎設施層獲取了一個列表資料,列表其中的物件結構如下,注意約束條件如果沒有pid
,預設為null
。
@Getter @Setter @ToString @Builder public class NodeEntity { /** * id */ private Long id; /** * 父id */ private Long pid; }
現在我們要將List<NodeEntity>
資料,按照屬性pid
進行Tree型層級封裝,並且支援多層級封裝。一般很容易想到遞迴的實現方法,接下來這篇文章使用一套通用的解決辦法,非遞迴實現結構轉換。
首先定義通用的Tree形資料介面。
public interface INodeDTO { /** * id * @return id */ public Long getId(); /** * pid * @return pid */ public Long getPid(); /** * 獲取Children * @return Children */ public List<INodeDTO> getChildren(); /** * 設定children * @param children children */ public void setChildren(List<INodeDTO> children); }
每個方法介面有詳細的註釋,無需多說。然後提供通用的轉換Function
。
/** * 非遞迴實現平鋪資料轉成Tree型結構 */ 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());
我們利用物件參照,淺拷貝的原理,通過迴圈查詢來組裝層級,最後根據pid==null
的資料一定是Tree型第一層的資料的條件進行過濾,篩選出第一層的資料組合成新的列表,達到目的。
//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; }
遞迴的實現先獲取所有根節點,方法builTree
總結根節點來建立一個樹結構,buildChilTree
為節點構建一個輔助樹,並拼接當前樹,遞迴呼叫buildChilTree
來不斷開啟當前樹的分支和葉子,直到沒有找到新的子樹, 完成遞迴,得到樹結構。
遞迴最大的問題可能堆疊太深,容易造成溢位,使用需要謹慎,而且從程式碼簡潔度來說,肯定是使用了非遞迴的方式更好。
遞迴程式碼還能進一步優化,比如改成尾遞迴的方式,有興趣的小夥伴可以嘗試一下。
範例只測試非遞迴實現方法。
那具體怎麼使用呢?首先我們通過implements
介面INodeDTO
,實現我們自己的業務DTO
。
@Getter @Setter @ToString @Builder public class NodeDTO implements INodeDTO { private Long id; private Long pid; List<INodeDTO> children; }
然後在我們Service
層組裝業務邏輯,這裡提供一個listBy
的條件查詢介面,從基礎設施層按照條件撈出List<NodeEntity>
,期望轉成內部包含層級關係的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); }
執行後輸出結果如下,經人工肉眼檢驗,達到Tree型層級結構。
上面講到了平鋪列表(List)轉樹形(Tree)結構,一般來說對於足夠後端資料轉成前端想要的結構了。但都支援了正向轉換,那麼反向轉換,即樹形(Tree)結構如何轉平鋪列表(List)呢?
遞迴實現,分為兩個函數,List<INodeDTO> flatten(List<INodeDTO> flatList)
接受外部呼叫,傳入待轉換的Tree
形結構。第一步便是收集所有的根節點,然後將所有的根節點傳入到遞迴函數List<INodeDTO> flatten(INodeDTO node, List<INodeDTO> flatList
中深度遍歷,最後彙總再使用distinct
做去重處理得到最終的list
結構。
/** * Flatten a Tree to a list using recursion(遞迴實現) * @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; }
在非遞迴,即迴圈的實現中,我們要用到dequeue
資料結構。
deque表示一個雙端佇列,這意味著可以從佇列的兩端新增和刪除元素。 deque的不同之處在於新增和刪除條目的不受限制的特性。
在實現中,ArrayDeque
將被用作LIFO
(即後進先出)資料結構(即堆疊)。
/** * 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
函數還原結構。
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); }
輸出結果不但包含Tree
形資料結構,還獲取到了list
資料,如下圖所示,至此,達到效果。
至此,遞迴和非遞迴分別實現list to tree
和tree to list
已完成,實現比較倉促,有很多細節處未處理好,希望看到的小夥伴及時指出,不勝感激。
到此這篇關於Java實現平鋪列表(List)互轉樹形(Tree)結構的文章就介紹到這了,更多相關Java List轉樹形Tree結構內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45