关于java:从字符串路径列表构造树结构

Construct a tree structure from list of string paths

我在列表中有一个字符串路径集合,例如[" x1 / x2 / x3"," x1 / x2 / x4"," x1 / x5"]。
我需要从该列表构造一个树状结构,可以对其进行迭代以获得漂亮的打印树。
像这样

1
2
3
4
5
     x1
    /  \\
   x5   x2
       /  \\
      x3  x4

有什么想法/建议吗?
我认为可以通过处理字符串列表来首先解决该问题。编辑:选择的正确答案是一种优雅的实现,其他建议也很好。


遵循可访问树的朴素实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
    }
}

访问者模式的界面:

1
2
3
4
5
6
7
8
9
10
11
interface Visitor< T > {

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

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

interface Visitable< T > {

    void accept(Visitor< T > visitor);
}

访问者模式的示例实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }
}

最后(!!!)一个简单的测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    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));

输出:

1
2
3
4
5
6
forest
  x1
    x2
      x3
      x4
    x5


只需用其定界符分隔每个路径,然后将它们逐个添加到树结构中。
即如果'x1'不存在,请创建此节点,如果确实存在,请转到该节点并检查是否有子'x2',依此类推...


我会一次把树做成一串。

制作一棵空树(它有一个根节点-我认为可能存在类似" x7 / x8 / x9"的路径)。

采用第一个字符串,将x1添加到根节点,然后将x2添加到x1,然后将x3添加到x2。

获取第二个字符串,看到x1和x2已经存在,将x4添加到x2。

对您拥有的每条路径执行此操作。


创建一个对象节点,其中包含一个父节点(节点)和一个子节点列表(节点)。

首先使用","分割字符串。对于每个分割的字符串,您都使用" /"分割字符串。
在根列表中搜索第一个节点标识符(例如x1)。
如果可以找到它,请使用该节点查找下一个节点标识符(例如x2)。

如果找不到节点,则将该节点添加到在现有列表中可以找到的最后一个节点。

创建列表结构后,可以将列表打印到屏幕上。我将使其递归。

未测试,仅是动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
    }
}


这是我从路径(文件夹)结构中做树的方式。也许应该以基本逻辑帮助某人。

节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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());
        }
    }
}


为数组中的每个字符串创建树。
只需为'/'分割路径,检查树中是否存在该节点,如果存在则继续前进...否则,创建一个新节点并将该节点添加到父节点的子节点中。

使用递归进行迭代。

以下是树节点的模型。

1
2
3
4
5
6
7
8
9
Class Node{
    string name;
    List<Node> childrens;

    Node(string name){
        this.name = name;
        this.childrens = new List<Node>();
    }
}