25

I have a collection of string paths like ["x1/x2/x3","x1/x2/x4","x1/x5"] in a list. I need to construct a tree-like structure from this list which can be iterated to get a pretty printed tree. like this

     x1
    /  \
   x5   x2
       /  \
      x3  x4

Any ideas/suggestions? I believe that the problem can be attacked first by processing the list of strings EDIT: The correct answer chosen was an elegant implementation, other suggestions were good too.

Nativ
  • 3,092
  • 6
  • 38
  • 69
sushant
  • 1,101
  • 2
  • 9
  • 15
  • Maybe you will be interested to read my current working implementation that will solve your (and mine) problem :) Take a look: http://stackoverflow.com/a/10935115/737636 – StErMi Jun 07 '12 at 15:39

7 Answers7

39

Follow an implementation of naive implementation of a visitable tree:

class Tree<T> implements Visitable<T> {

    // NB: LinkedHashSet preserves insertion order
    private final Set<Tree> children = new LinkedHashSet<Tree>();
    private final T data;

    Tree(T data) {
        this.data = data;
    }

    void accept(Visitor<T> visitor) {
        visitor.visitData(this, data);

        for (Tree child : children) {
            Visitor<T> childVisitor = visitor.visitTree(child);
            child.accept(childVisitor);
        }
    }

    Tree child(T data) {
        for (Tree child: children ) {
            if (child.data.equals(data)) {
                return child;
            }
        }

        return child(new Tree(data));
    }

    Tree child(Tree<T> child) {
        children.add(child);
        return child;
    }
}

interfaces for Visitor Pattern:

interface Visitor<T> {

    Visitor<T> visitTree(Tree<T> tree);

    void visitData(Tree<T> parent, T data);
}

interface Visitable<T> {

    void accept(Visitor<T> visitor);
}

sample implementation for Visitor Pattern:

class PrintIndentedVisitor implements Visitor<String> {

    private final int indent;

    PrintIndentedVisitor(int indent) {
        this.indent = indent;
    }

    Visitor<String> visitTree(Tree<String> tree) {
        return new IndentVisitor(indent + 2);
    }

    void visitData(Tree<String> parent, String data) {
        for (int i = 0; i < indent; i++) { // TODO: naive implementation
            System.out.print(" ");
        }

        System.out.println(data);
    }
}

and finally (!!!) a simple test case:

    Tree<String> forest = new Tree<String>("forest");
    Tree<String> current = forest;

    for (String tree : Arrays.asList("x1/x2/x3", "x1/x2/x4", "x1/x5")) {
        Tree<String> root = current;

        for (String data : tree.split("/")) {
            current = current.child(data);
        }

        current = root;
    }

    forest.accept(new PrintIndentedVisitor(0));

output:

forest
  x1
    x2
      x3
      x4
    x5
dfa
  • 114,442
  • 31
  • 189
  • 228
12

Just split each path by its delimiter and then add them to a tree structure one by one.
i.e. if 'x1' does not exist create this node, if it does exist go to it and check if there is a child 'x2' and so on...

S1LENT WARRIOR
  • 11,704
  • 4
  • 46
  • 60
Roland Ewald
  • 4,630
  • 3
  • 35
  • 49
5

I'd make the tree one string at a time.

Make an empty tree (which has a root node - I assume there could be a path like "x7/x8/x9").

Take the first string, add x1 to the root node, then x2 to x1, then x3 to x2.

Take the second string, see that x1 and x2 are already there, add x4 to x2.

Do this for every path you have.

David Johnstone
  • 24,300
  • 14
  • 68
  • 71
2

Create an Object Node which contains a parent (Node) and a List of children (Node).

First split the string using ",". For every splitted string you split the string using "/". Search for the first node identifier (e.g x1) in the root list. If you can find it, use the node to find the next node identifier (e.g. x2).

If you can not find a node, add the node to the last node you was able to find in the existing lists.

After you have created the list structure, you can print the list to the screen. I would make it recursive.

NOT TESTED, just an animation

public void print(List nodes, int deep) {
    if (nodes == null || nodes.isEmpty()) {
        return;
    }

    StringBuffer buffer = new StringBuffer();
    for (int i = 0; i < deep; i++) {
        buffer.append("---");
    }

    for (Iterator iterator = nodes.iterator(); iterator.hasNext();) {
        Node node = (Node)iterator.next();

        System.out.println(buffer.toString() + " " + node.getIdentifier());

        print(node.getChildren(), deep + 1);
    }
}
Markus Lausberg
  • 12,177
  • 6
  • 40
  • 66
1
public class Menu {

    private String path;
    private List<Menu> children;

    public Menu(String path) {
        this.path = path;
        children = new ArrayList<>();
    }

    public void addChild(Menu child) {
        children.add(child);
    }

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

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Menu getChild(String data) {
        for (Menu n : children)
            if (n.path.equals(data)) {return n;}

        return null;
    }


}

Tree builder class:

public class MenuTree {
   
    private Menu root;

    public MenuTree() {
        root = new Menu("");
    }

    public void add(String str) {
        Menu current = root;
        StringTokenizer s = new StringTokenizer(str, "/");
        while (s.hasMoreElements()) {
            str = (String) s.nextElement();
            Menu child = current.getChild(str);
            if (child == null) {
                current.addChild(new Menu(str));
                child = current.getChild(str);
            }
            current = child;
        }
    }

    public JSONObject toJSON() {
        try {
            return new JSONObject(new ObjectMapper().writeValueAsString(this.root));
        } catch (JsonProcessingException e) {
           
            return null;
        }

    }
}

Usage:

  String slist[] = new String[]{
                "mnt/sdcard/folder1/a/b/file1.file",
                "mnt/sdcard/folder1/a/b/file2.file",
                "D/a/b/c.file",

        };

        MenuTree t = new MenuTree();
        for (String s : slist) {
            t.add(s);
        }
    
        System.out.println(t.toJSON().toString());

JSONObject result:

{"path":"","children":[{"path":"mnt","children":[{"path":"sdcard","children":[{"path":"folder1","children":[{"path":"a","children":[{"path":"b","children":[{"path":"file1.file","children":[]},{"path":"file2.file","children":[]}]}]}]}]}]},{"path":"D","children":[{"path":"a","children":[{"path":"b","children":[{"path":"c.file","children":[]}]}]}]}]}

uugan
  • 27
  • 6
0

Make your tree for every string in array. Just split path for '/' , check whether the node exists in your tree or not, if it exists then move on... otherwise create a new node and add this node in childrens of parent node.

Iterate using recursion.

Following is model for tree's node.

Class Node{
    string name;
    List<Node> childrens;

    Node(string name){
        this.name = name;
        this.childrens = new List<Node>();
    }
}
Luca Angioloni
  • 2,243
  • 2
  • 19
  • 28
Mr_Hmp
  • 2,474
  • 2
  • 35
  • 53
0

This is way how I am doing tree from path (folders) structure. Maybe should help someone with basic logic.

Node:

public class Node {
    private String path;
    private List<Node> children;

    public Node(String path) {
        this.path = path;
        children = new ArrayList<>();
    }

    public String getName() {
        return getName(path);
    }

    private String getName(String path) {
        String[] split = path.split("\\\\");
        return split[split.length - 1];
    }

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

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

    public String getPath() {
        return path;
    }
}

FilesTree:

public class FilesTree {
    private static final Logger log = Logger.getLogger(FilesTree.class.getName());

    private FilesTree() {}

    private static void createTree(Node root, List<String> paths) {
        for (String path : paths) {
            addNode(root, Arrays.asList(path.split("\\\\")), "");
        }
    }

    private static void addNode(Node node, List<String> path, String nodePath) {
        if (!path.isEmpty()) {
            nodePath = nodePath.equals("") ? path.get(0) : String.format("%s\\%s", nodePath, path.get(0));
        }

        if (node.getChildren().isEmpty() && path.size() == 1) {
            node.addChild(new Node(nodePath));
        } else if (!node.getChildren().isEmpty()) {
            for (Node actual : node.getChildren()) {
                if (actual.getName().equals(path.get(0))) {
                    addNode(actual, path.subList(1, path.size()), nodePath);
                    return;
                }
            }
            node.addChild(new Node(nodePath));
        } else {
            log.info("Without children but with size: " + path.size());
        }
    }
}
PetKap
  • 31
  • 6
  • Your answer isn't very informative; maybe you can explain the logic if you want to help others. – dspencer Mar 13 '20 at 14:32
  • What you are missing? Explanation of methods? – PetKap Mar 16 '20 at 08:04
  • This question isn't new; you could let us know why you prefer this solution, rather than the accepted answer and provide a brief description of your solution's logic. – dspencer Mar 16 '20 at 08:23
  • 1
    Ah, okay. I tried this solutions, but they were quite overcomplicated and I think I am bringing more easier solution. – PetKap Mar 16 '20 at 10:00