I am wondering if there is a better way to traverse a list of trees.
Here is my current algorithm:
- Collect the parent IDs in a Map
- Traverse all the groups and find ones where their ID is not in the map
- Calculate each of those groups overall status, based on their data
- Go through them again and find their parent
- Check if the parent has been visited before, if not
- Find its child groups and its data and calculate an overall status
I have included 6 classes below:
Main.java
~ Builds the groups, sets the status, and prints the tree(s)NodeUtils.java
~ Main library code for traversing the nodesNode.java
~ Simple interface that represents a nodeGroupNode.java
~ Represents a group that can hold data and child groupsDataNode.java
~ Stores a status and other dataStatus.java
~ Represents a status of NORMAL, WARNING, CRITICAL, and UNKNOWN
Here is my current output, which is correct:
A: (WARNING) {0}
C: (WARNING) {2}
B: (CRITICAL) {1}
D: (CRITICAL) {1}
E: (WARNING) {1}
F: (UNKNOWN) {0}
- The output is group label, status, child count
- Indentation is added for each depth
Main.java
import java.util.List;
public class Main {
public static void main(String[] args) {
List<GroupNode> groups = createGroups();
NodeUtils.calculateStatuses(groups);
NodeUtils.drawTree(groups);
}
private static List<GroupNode> createGroups() {
GroupNode groupA = new GroupNode(1, "A");
GroupNode groupB = new GroupNode(2, "B");
GroupNode groupC = new GroupNode(3, "C");
GroupNode groupD = new GroupNode(4, "D");
GroupNode groupE = new GroupNode(5, "E");
GroupNode groupF = new GroupNode(6, "F");
DataNode<String> data1 = new DataNode<>(1, "AC1");
DataNode<String> data2 = new DataNode<>(2, "AC2");
DataNode<String> data3 = new DataNode<>(3, "BD1");
DataNode<String> data4 = new DataNode<>(4, "BE1");
DataNode<String> data5 = new DataNode<>(5, "B1");
groupC.setParentId(groupA.getId());
groupD.setParentId(groupB.getId());
groupE.setParentId(groupB.getId());
groupF.setParentId(groupE.getId());
groupC.getChildren().add(data1);
groupC.getChildren().add(data2);
groupD.getChildren().add(data3);
groupE.getChildren().add(data4);
groupB.getChildren().add(data5);
data1.setStatus(Status.NORMAL);
data2.setStatus(Status.WARNING);
data3.setStatus(Status.CRITICAL);
data4.setStatus(Status.WARNING);
data5.setStatus(Status.NORMAL);
return List.of(groupA, groupB, groupC, groupD, groupE, groupF);
}
}
NodeUtils.java
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class NodeUtils {
public static void calculateStatuses(List<GroupNode> groups) {
Map<Long, Boolean> parentIds = buildParentIdMap(groups);
for (GroupNode group : groups) {
if (!parentIds.containsKey(group.getId())) {
// Does not have any child groups
group.setStatus(calculateMaxStatus(group.getChildren()));
}
}
for (GroupNode group : groups) {
if (!parentIds.containsKey(group.getId())) {
// If unvisited
if (!parentIds.get(group.getParentId())) {
GroupNode parent = findParentGroup(group, groups);
List<GroupNode> childGroups = findChildGroups(parent, groups);
List<Node> groupsAndData = Stream.concat(childGroups.stream(), parent.getChildren().stream()).collect(Collectors.toList());
parent.setStatus(calculateMaxStatus(groupsAndData));
parentIds.put(group.getParentId(), true); // Visited
}
}
}
}
public static void drawTree(List<GroupNode> groups) {
List<GroupNode> topLevel = groups.stream().filter(NodeUtils::isRootNode).collect(Collectors.toList());
topLevel.forEach(node -> visitGroup(node, 0, groups));
}
private static void visitGroup(GroupNode group, int depth, List<GroupNode> groups) {
String indent = "\t".repeat(depth);
System.out.printf("%s%s: (%s) {%d}%n", indent, group.getLabel(), group.getStatus(), group.getChildren().size());
findChildGroups(group, groups).forEach(child -> visitGroup(child, depth + 1, groups));
}
private static GroupNode findParentGroup(GroupNode group, List<GroupNode> groups) {
return groups.stream().filter(curr -> curr.getId() == group.getParentId()).findFirst().orElse(null);
}
private static List<GroupNode> findChildGroups(GroupNode group, List<GroupNode> groups) {
return groups.stream().filter(curr -> curr.getParentId() == group.getId()).collect(Collectors.toList());
}
private static <E extends Node> Status calculateMaxStatus(List<E> nodes) {
Status status = Status.UNKNOWN;
for (E node : nodes) {
if (node.getStatus().getWeight() < status.getWeight()) {
status = node.getStatus();
}
}
return status;
}
private static boolean invalidId(long id) {
return id < 1;
}
private static boolean isRootNode(GroupNode node) {
return Optional.of(node).map(GroupNode::getParentId).map(NodeUtils::invalidId).orElse(false);
}
private static Map<Long, Boolean> buildParentIdMap(List<GroupNode> groups) {
Map<Long, Boolean> parentIds = new HashMap<>();
for (GroupNode group : groups) {
if (group.getParentId() > 0) {
parentIds.put(group.getParentId(), false);
}
}
return parentIds;
}
}
Node.java
public interface Node {
long getId();
String getLabel();
Status getStatus();
}
GroupNode.java
import java.util.ArrayList;
import java.util.List;
public class GroupNode implements Node {
private long id;
private long parentId;
private String label;
private Status status;
private List<Node> children;
public GroupNode(long id, String label) {
this.id = id;
this.label = label;
this.status = Status.UNKNOWN;
this.children = new ArrayList<>();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getParentId() {
return parentId;
}
public void setParentId(long parentId) {
this.parentId = parentId;
}
@Override
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public List<Node> getChildren() {
return children;
}
public void setChildren(List<Node> children) {
this.children = children;
}
}
DataNode.java
public class DataNode<T> implements Node {
private long id;
private String label;
private Status status;
private T data;
public DataNode(long id, String label) {
this.id = id;
this.label = label;
this.status = Status.UNKNOWN;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
Status.java
public enum Status {
NORMAL(100),
WARNING(10),
CRITICAL(0),
UNKNOWN(Integer.MAX_VALUE);
private int weight;
Status(int weight) {
this.weight = weight;
}
public int getWeight() {
return weight;
}
}
children
. Can the group nodes be modified to contain their child groups? \$\endgroup\$