Skip to content

Commit

Permalink
feat: list2tree method add
Browse files Browse the repository at this point in the history
  • Loading branch information
hz-hanjm committed Nov 14, 2021
1 parent bef0c8a commit a8b7fd0
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 0 deletions.
104 changes: 104 additions & 0 deletions src/main/java/org/apache/commons/lang3/TreeUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3;


import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* list and tree translate util class
* @author <a href="mailto:hjm0928@gmail.com">ejums</a>
*/
public class TreeUtils {
/**
* list to tree; this method will change original list!
* @param list original list
* @param topConsumer the top node will be consumed by it
* @param idGetter to get id from node
* @param pidGetter to get parent id from node
* @param comparator to sort same level node
* @param generator to subset parent relation association is carried out operation
* @param <T> the type of the object
* @param <E> the type of the id, pid
* @return tree list
*/
public static <T, E> List<T> buildTree(List<T> list, Consumer<T> topConsumer, Function<T, E> idGetter,
Function<T, E> pidGetter, Comparator<T> comparator,
BiConsumer<T, List<T>> generator){

Set<E> idSet = new HashSet<>();
Map<E, List<T>> pidGroup = new HashMap<>(list.size() >> 1);
// make group by pid
for (T t : list) {
idSet.add(idGetter.apply(t));
final E pid = pidGetter.apply(t);
final List<T> itemList = pidGroup.getOrDefault(pid, new ArrayList<>());
itemList.add(t);
pidGroup.put(pid, itemList);
}
// get the top node: pid cannot find in id set
List<T> topList = pidGroup.keySet().stream()
.filter(v -> !idSet.contains(v))
// find all top node
.flatMap(v -> pidGroup.getOrDefault(v, Collections.emptyList()).stream())
// consumer this top node
.peek(v -> {
if (topConsumer != null) {
topConsumer.accept(v);
}
})
.collect(Collectors.toList());
// if comparator set, make top node list sorted
if (comparator != null) {
topList.sort(comparator);
}

// build tree with no recursive
List<T> children = new ArrayList<>();
List<T> temp = new ArrayList<>(topList);
while (!temp.isEmpty()) {
temp.forEach(v -> {
E apply = idGetter.apply(v);
if (apply != null) {
List<T> child = pidGroup.get(apply);
if (child != null) {
if (comparator != null) {
child.sort(comparator);
}
generator.accept(v, child);
children.addAll(child);
}
}
});
temp.clear();
temp.addAll(children);
children.clear();
}
return topList;
}
}
194 changes: 194 additions & 0 deletions src/test/java/org/apache/commons/lang3/TreeUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.commons.lang3;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TreeUtilsTest {

@Test
public void testPojoBuildTee(){
List<TreeNode> list = new ArrayList<>();
list.add(new TreeNode(2, null, "s1"));
list.add(new TreeNode(1, null, "s0"));
list.add(new TreeNode(10, 1, "s10"));
list.add(new TreeNode(311, 31, "s311"));
list.add(new TreeNode(11, 1, "s11"));
list.add(new TreeNode(31, 3, "s31"));
list.add(new TreeNode(112, 11, "s112"));
final List<TreeNode> treeList = TreeUtils.buildTree(list,
node -> {
node.setTop(true);
node.setLevel(1);
node.setParentHierarchyId(String.valueOf(node.getId()));
node.setLeaf(false);
},
TreeNode::getId,
TreeNode::getPid,
Comparator.comparing(TreeNode::getId),
((node, children) -> {
node.setLeaf(false);
for (TreeNode child : children) {
child.setParentHierarchyId(node.getParentHierarchyId() + ">" + child.getId());
child.setLeaf(true);
child.setLevel(node.getLevel() + 1);
child.setTop(false);
}
node.setChildren(children);
}));
Assertions.assertTrue(!treeList.isEmpty() && !treeList.get(0).getChildren().isEmpty());
}

@Test
public void testMapBuildTree(){
List<Map<Object, Object>> list = new ArrayList<>();
list.add(parseMap("100", "s100", null));
list.add(parseMap("200", "s200", "000"));
list.add(parseMap("110", "s110", "100"));
list.add(parseMap("120", "s120", "100"));
list.add(parseMap("210", "s210", "200"));
list.add(parseMap("211", "s211", "210"));
final List<Map<Object, Object>> treeList = TreeUtils.buildTree(list,
(node) -> {
node.put("top", true);
node.put("level", 1);
node.put("parentHierarchyId", node.get("id"));
node.put("leaf", false);
},
node -> node.get("id"),
node -> node.get("pid"),
Comparator.comparing(node -> (String) node.get("id")),
(node, children) -> {
node.put("leaf", false);
for (Map<Object, Object> child : children) {
child.put("parentHierarchyId", (String) node.get("parentHierarchyId") + node.get("id"));
child.put("leaf", false);
child.put("level", (Integer) node.get("level") + 1);
child.put("top", false);
}
node.put("children", children);
});
//noinspection unchecked
Assertions.assertTrue(!treeList.isEmpty()
&& !((List<Map<Object, Object>>) treeList.get(0)
.getOrDefault("children", Collections.emptyList())).isEmpty());
}

private Map<Object, Object> parseMap(String id, String name, String pid){
Map<Object, Object> map = new HashMap<>();
map.put("id", id);
map.put("name", name);
map.put("pid", pid);
return map;
}

/**
* tree node
* @author <a href="mailto:hjm0928@gmail.com">ejums</a>
*/
public class TreeNode{
// base field
private Integer id;
private Integer pid;
private String name;
// compute field
private Integer level;
private String parentHierarchyId;
private Boolean top;
private Boolean leaf;
private List<TreeNode> children;

public TreeNode(Integer id, Integer pid, String name){
this.id = id;
this.pid = pid;
this.name = name;
}

public Integer getId() {
return id;
}

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

public Integer getPid() {
return pid;
}

public void setPid(Integer pid) {
this.pid = pid;
}

public String getName() {
return name;
}

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

public Integer getLevel() {
return level;
}

public void setLevel(Integer level) {
this.level = level;
}

public String getParentHierarchyId() {
return parentHierarchyId;
}

public void setParentHierarchyId(String parentHierarchyId) {
this.parentHierarchyId = parentHierarchyId;
}

public Boolean getTop() {
return top;
}

public void setTop(Boolean top) {
this.top = top;
}

public Boolean getLeaf() {
return leaf;
}

public void setLeaf(Boolean leaf) {
this.leaf = leaf;
}

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

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

0 comments on commit a8b7fd0

Please sign in to comment.