skip to Main Content

I was trying to implement a class Node to build a tree of Nodes. Basically, each Node can have children, so if I specify multiple nodes I can build a tree out of it.
As an example:

node1 (the root) has node2 and node3 as children
node2 has node4 and node5 as children

The problem I am having problems to solve is to build this tree and find all children of a given element (in this case node1 would have 4 children, since it has node2 and node3 in the first place, and node2 has 2 children, so in total 4).
Does anyone have any suggestion?

EDIT:

package ex1;

import java.sql.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class Node {

  private String name;
  private String description;

  private ArrayList<Node> children = new ArrayList<>();

  Node(String name, String description){
    this.name = name;
    this.description = description;
  }

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

  private void setDescription(String description) {
    this.description = description;
  }

  public void addChildren(Node child) {
    this.children.add(child);
  }

  public String getName() {
    return this.name;
  }

  public String getDescription() {
    return this.description;
  }

  public boolean hasDescription() {
    return !description.isEmpty();
  }

  public Collection<Node> getChildren() {
    return this.children;
  }

  /*
  public Node findNodeByName(String name, Node t) {
    if (t.getName().equals(name))
      return t;
    t.getChildren().forEach(node -> node.findNodeByName(name,node));
    return null;
  }*/

  public Node findNodeByName(String name, Node t){
    if(t.getName().equals(name)){
      return t;
    }
    else if (t.getChildren().size() != 0){
      for(Node c: children){
        Node ret = c.findNodeByName(name,c);
        if(ret != null){
          return ret;
        }
      }
    }
    return null;
  }



  // IMPORTANT: Don't change this method!
  private String toString(int indentNo) {
    String indent = "t".repeat(indentNo);
    StringBuffer b = new StringBuffer();
    b.append(indent);
    b.append(getClass().getSimpleName() + " '" + getName() + "' ");

    if (hasDescription()) {
      b.append("(description: " + getDescription() + ")");
    }

    b.append("n");

    for (Node node : getChildren()) {
      b.append(node.toString(indentNo + 1));
    }

    return b.toString();
  }

  @Override
  public String toString() {
    return toString(0);
  }
}

Method where I make use of the class:

Path path = Path.of(pathname);

String fileContent = null;
try {
   fileContent = Files.readString(path);
} catch (IOException e) {
   throw new RuntimeException(e);
}
List<String> lines = new ArrayList<>(Arrays.asList(fileContent.split("n")));
String[] firstLine = lines.get(0).split(",");
Node parentNode = new Node(firstLine[0], firstLine[1]);
lines.remove(0);

/* This was just to test findNodeByName
for(String line: lines) {
   String[] params = line.split(",");
   System.out.println(params[2] + ": " + (parentNode.findNodeByName(params[2], parentNode) != null));
}*/
//Now we read all remaining data

Node tmpNode;
for(String line: lines) {
   String[] params = line.split(",");
   if (parentNode.findNodeByName(params[2])==null || parentNode.findNodeByName(params[0])!=null) //if parent was not found or name already exists
         throw new IOException();
   tmpNode = parentNode.findNodeByName(params[2]);
   tmpNode.addChildren(new Node(params[0],params[1]));
}

CSV file I am getting the data from:

uni,"This is my university folder",
firstyear,,uni
secondyear,,uni
analysis,"folder for the analysis course",firstyear
ai,"folder for the artificial intelligence course",secondyear
db,"folder for the database course",firstyear

3

Answers


  1. Here is some sample code that could help (explanation below):

    Main class:

    class Main {
      public static void main(String[] args) {
        Node v1 = new Node(1);
        Node v2 = new Node(2);
        Node v3 = new Node(3);
        Node v4 = new Node(4);
        Node v5 = new Node(5);
        v1.addChild(v2);
        v1.addChild(v3);
        v2.addChild(v4);
        v2.addChild(v5);
        v1.printChildren();
      }
    }
    

    Node class:

    import java.util.*;
    class Node{
      private int val;
      private ArrayList<Node> children = new ArrayList<Node>();
      public Node(int v){
        val = v;
      }
      public void addChild (Node c){
        children.add(c);
      }
      public void printChildren(){
        if (children.size() != 0){
          System.out.print("Children of Node " + getValue() + ": ");
          for(Node c: children){
            System.out.print("Node " + c.getValue() + " ");
          }
          System.out.println();
          for(Node c: children){
            c.printChildren();
          }
        }
      }
      public int getValue(){
        return val;
      }
    }
    

    Output:

    Children of Node 1: Node 2 Node 3 
    Children of Node 2: Node 4 Node 5 
    

    Ok so in our node class, let’s say each node will have an integer value, val. That is our first private instance variable. Second, each node will have a list of children nodes, children.

    When we first declare our nodes, they will have integer values, as shown in our constructor.

    After we define our nodes, we can add some nodes as children to other nodes (v2 and v3 are children to v1, and v4 and v5 are children to v2).

    Now we need to print them. We will use a recursive approach for this. If the node we are printing the children of has children (the length of our children ArrayList is nonzero), then we will first iterate through that list, and print out the children of our current node. Afterwards, we again iterate through each child and use the same method (recursion) to print out the children of that node.

    I hope this helped! Please let me know if you need any further help or clarification 🙂

    EDIT:

    Added a getName() method:

    public String getName(){
       return "Node" + getValue();
    }
    

    Added the requested method:

      public Node findChildNodeByValue(int v){
        if(getValue() == v){
          System.out.println(getName() + " has the value");
          return new Node(getValue());
        }
        else if (children.size() != 0){
          for(Node c: children){
            Node ret = c.findChildNodeByValue(v);
            if(ret != null){
              return ret;
            }
          }
        }
        return null;
      }
    

    Quick Explanation: Very similar to the original method, we use a recursive approach to iterate through each nodes’ children: Once we reach a node with no more children, we return null. Once we reach the node with the given value, we return a copy of that node, which will be sent back to wherever the function was called..

    Also edited main method:

    Node v1 = new Node(1);
    Node v2 = new Node(2);
    Node v3 = new Node(3);
    Node v4 = new Node(4);
    Node v5 = new Node(5);
    v1.addChild(v2);
    v1.addChild(v3);
    v2.addChild(v4);
    v2.addChild(v5);
    // v1.printChildren();
    Node valNode = v1.findChildNodeByValue(5);
    System.out.println(valNode.getName());
    

    Output:

    Node5 has the value
    Node5
    

    SECOND EDIT:

    Change the method to look like this:

      public Node findNodeByName(String name){
        if(getName().equals(name)){
          Node t = new Node(getName(), getDescription());
          return t;
        }
        else if (getChildren().size() != 0){
          for(Node c: children){
            Node ret = c.findNodeByName(name);
            if(ret != null){
              return ret;
            }
          }
        }
        return null;
      }
    

    The main method should look like this:

    Node v1 = new Node("a","aa");
    Node v2 = new Node("b","bb");
    Node v3 = new Node("c","cc");
    Node v4 = new Node("d","dd");
    Node v5 = new Node("e","ee");
    v1.addChildren(v2);
    v1.addChildren(v3);
    v2.addChildren(v4);
    v2.addChildren(v5);
    
    System.out.println(v1.findNodeByName("e"));
    

    Output:

    Node 'e' (description: ee)
    

    THIRD EDIT:

    Added a new method:

    public void setChildren(ArrayList<Node> c){
       children = c;
    }
    

    Edited method:

      public Node findNodeByName(String name){
        if(getName().equals(name)){
          Node t = new Node(getName(), getDescription());
          t.setChildren(getChildren());
          return t;
        }
        else if (getChildren().size() != 0){
          for(Node c: children){
            Node ret = c.findNodeByName(name);
            if(ret != null){
              return ret;
            }
          }
        }
        return null;
      }
    

    Main Method:

    Node v1 = new Node("a","aa");
    Node v2 = new Node("b","bb");
    Node v3 = new Node("c","cc");
    Node v4 = new Node("d","dd");
    Node v5 = new Node("e","ee");
    Node v6 = new Node("f","ff");
    v1.addChildren(v2);
    v1.addChildren(v3);
    v2.addChildren(v4);
    v2.addChildren(v5);
    v4.addChildren(v6);
    
    Node vNew = v1.findNodeByName("d");
    System.out.println(vNew);
    System.out.println(vNew.getChildren());
    

    Output:

    Node 'd' (description: dd)
        Node 'f' (description: ff)
    
    [Node 'f' (description: ff)
    ]
    
    Login or Signup to reply.
  2. As per your requirement, you are looking for all descendants / children-of-children of a particular node. Then breadth-first depth-search is fit more to this use case. There are already tons of discussions around these algorithms. For instance:

    Breadth First Search and Depth First Search

    You are already thinking in the right direction related to its DataStructure. One thing I would suggest use java generics so that it can support multiple data-type as needed.

    class Node<T> {
        
        T value;
        List<T> children;
        
        Node(T t) {
            this.value = t;
            children = new ArrayList<T>();
        }
        
        void addChild(T child) {
            children.add(child);
        }
    }
    
    Login or Signup to reply.
  3. The return value of the recursive method call is dismissed.

    The line

    t.getChildren().forEach( node -> findNodeByName(name, node));
    

    induces the recursive invocation, but the return value is not used to form the return value of the enclosing method.

    Instead we need something like

    for (Node node : t.getChildren()) {
        Node result = findNodeByName(name, node);
        if (null != result) {
            return result;
        }
    }
    

    or with streams

    return t.getChildren()
        .map(node -> findNodeByName(name, node))
        .filter(Objects::nonNull)
        .findAny();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search