-3

I have a list of nodes, and each node might have a list of subNodes (the number of levels are unknown):

class Node {
    int score;
    boolean selected;
    List<Node> subNodes;
}

Here's how an hypothetical structure might look like:

NODE
  + NODE
      + NODE
      + NODE
          + NODE
      + NODE
  + NODE
      + NODE
          + NODE
          + NODE
              + NODE
              + NODE

Combinations are just countless. I need a way to sum NODE.score for all those nodes that have NODE.selected set to true, possibly using Java 8 features. Any hints would be really appreciated.

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
j3d
  • 9,492
  • 22
  • 88
  • 172

3 Answers3

2

Something like:

public int recursiveTotal(final Node node) {
    //node not select, don't count the node or any of its subnodes
    if (!node.selected) {
        return 0;
    }
    //no subnodes, only node score counts
    if (node.subNodes.isEmpty()) {
        return node.score;
    }
    //node has subnodes, recursively count subnode score + parent node score
    int totalScore = node.score;
    for (final Node subNode : node.subNodes) {
        totalScore += recursiveTotal(subNode);
    }
    return totalScore;
}

Coded using stackoverflow as an IDE, no guarantee against compilation errors ;)

Paul Creasey
  • 28,321
  • 10
  • 54
  • 90
Tobb
  • 11,850
  • 6
  • 52
  • 77
2

Create a recursive method in your Node class which returns a stream of nodes concatenating a stream of the parent node and the sub nodes:

class Node {
    int score;
    boolean selected;
    List<Node> subNodes;

    public Stream<Node> streamNodes() {
        return Stream.concat(Stream.of(this), subNodes.stream().flatMap(Node::streamNodes));
    }
}

and use it like below to stream over your list:

List<Node> myNodes = //your list
int sum = myNodes.stream()
                 .flatMap(Node::streamNodes)
                 .filter(Node::isSelected)
                 .mapToInt(Node::getScore)
                 .sum();
Eritrean
  • 15,851
  • 3
  • 22
  • 28
1

TL;DR

Judging by the structure, you've provided each Node in your List is the root of an N-ary Tree data structure (I assume that there are no circles).

And in order to get the required data, we can utilize one of the classic tree-traversal algorithms. In case when the average depth is lower than the average width Depth first search algorithm would be more suitable because it would be more space-efficient, in the opposite situation it would be better to use Breadth first search. I'll go with DFS.

It's easier to come up with a recursive implementation, so I'll start with it. But it has no practical value in Java, hence we would proceed with a couple of improvements.

Streams + recursion

You can create a helper-method responsible for flattening the nodes which would be called from the stream.

List<Node> nodes = // initializing the list
        
long totalScore = nodes.stream()
    .flatMap(node -> flatten(node).stream())
    .filter(Node::isSelected)
    .mapToLong(Node::getScore)
    .sum();

Recursive auxiliary method:

public static List<Node> flatten(Node node) {
    if (node.getSubNodes().isEmpty()) {
        return List.of(node);
    }
    
    List<Node> result = new ArrayList<>();
    result.add(node);
    node.getSubNodes().forEach(n -> result.addAll(flatten(n)));
    return result;
}

No recursion

To avoid StackOverflowError method flatten() can be implemented without recursion by polling and allocating new nodes on the stack (represented by an ArrayDeque) iterativelly.

public static List<Node> flatten(Node node) {
    
    List<Node> result = new ArrayList<>();
    Deque<Node> stack = new ArrayDeque<>();
    stack.add(node);
    
    while (!stack.isEmpty()) {
        Node current = stack.poll();
        result.add(current);

        current.getSubNodes().forEach(stack::push);
    }
    return result;
}

No recursion & No intermediate data allocation

Allocating intermediate data in the form of nodes which eventually would not be used is impractical.

Instead, we can make the auxiliary method to be responsible for calculating the total score produced by summarizing the score of each selected node in the tree of nodes.

For that we need to perform isSelected() while traversing the tree.

List<Node> nodes = // initializing the list
        
long totalScore = nodes.stream()
    .mapToLong(node -> getScore(node))
    .sum();
public static long getScore(Node node) {
    long total = 0;
    
    Deque<Node> stack = new ArrayDeque<>();
    stack.push(node);
    
    while (!stack.isEmpty()) {
        Node current = stack.poll();
        if (current.isSelected()) total += current.getScore();

        current.getSubNodes().forEach(stack::push);
    }
    return total;
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46