简体   繁体   中英

Build a tree from file paths

I'm trying to create a tree view from file paths, which can be added and removed dinamically, for instance:

A/B/C/D/file1.txt
A/B/D/E/file2.txt
A/B/D/G/file3.txt
A/B/D/G/file4.txt

My tree, however, has a requirement that paths with no child items (files) should be collapsed in one node. For the paths above it would yield:

A/B
  |---C/D
       file1.txt   
  |---D
     |---E
     |    file2.txt
     |---G
          file3.txt
          file4.txt

Any thoughts? Creating the tree is easy, but I can't get past that extra condition... I assume I'd have to use some kind of recursion as I go adding items and breaking the paths as we find that a certain path has more children (and then doing the same recursively?). Should I use some kind of trie? Would it work when the same path can have multiple files?... Thanks!

Let's begin by a simple solution to print the tree as it actually is:

function browseTree(node)
{
    // ...print node...

    // Visit recursively the children nodes:
    for (var child: node.children)
    {
        browseTree(child);
    }
}

Now, let's modify it to "shorten" the single-folder paths:

function browseTree(node)
{
    // Before printing, accumulate as many straight folders as possible:
    var nodeName=node.name
    while (hasJustOneFolder(node))
    {
        // This loop steps deeper in the tree:
        node=node.children[0]
        nodeName+="/"+node.name;
    }

    // ...print node...

    // Last, visit recursively the non-unique children nodes:
    for (var child: node.children)
    {
        browseTree(child);
    }
}

function hasJustOneFolder(node)
{
    return node.children.length==1 && node.children[0].isFolder();
}

Given your requirements, it seems like adding a new file to, say, C, wouldn't imply recursive operations.

If you add file5.txt to folder C, you have to transform C/D in a node C having 2 children: file5.txt and a new node called D . D will have the same children as the old node C/D . You can then erase node C/D .

However, this will not affect node A/B , because folder A will still have only one folder (B) as a child. Thus you could solve the problem making only local changes.

I create a sample code with custom tree node format with Map and the print function is a generator function to get line by line the path of the tree.

// Node
class NodePath {
    constructor(e) {
        this.isFolder = e.isFolder;
        this.name = e.name;
        this.childs = new Map();
    }
}

// Make path tree
function makePathsTree(paths) {
    const treeRoot = new NodePath({isFolder: true, name: "*"});

    for (const path of paths) {
        // Set current post as root
        let curPos = treeRoot;

        // For each part
        const parts = path.split("/");
        while (parts.length) {
            // Get cur
            const curPart = parts.shift();

            // Get child node, create if not exists
            let childNode = curPos.childs.get(curPart);
            if (!childNode) {
                childNode = new NodePath({
                    isFolder: !!parts.length,
                    name: curPart,
                });
                curPos.childs.set(curPart, childNode)
            }

            // Update cur post to child node
            curPos = childNode;
        }
    }

    // Return tree
    return treeRoot;
}

// Generator function prevent huge large file system strings
function *printPathsTree(node, offset = 0, prev = "") {
    // Offset str
    const offsetStr = " ".repeat(offset);

    // Is folder
    if (!node.isFolder) {
        yield `${offsetStr}${prev}${node.name}`;
        return;
    }

    // If one child and is folder, merge paths
    if (node.childs.size === 1) {
        const child = node.childs.values().next().value;
        if (child.isFolder === true) {
            for (const childData of printPathsTree(child, offset, `${prev}${node.name}/`)) {
                yield childData;
            }
            return;
        }
    }

    // Print node name
    yield `${offsetStr}${prev}${node.name}`;

    // For each child, print data inside
    for (const child of node.childs.values()) {
        for (const childData of printPathsTree(child, offset + prev.length, "|---")) {
            yield childData;
        }
    }
}

// == CODE ==
console.log("WITH ROOT:");
const tree = makePathsTree([
    "A/B/C/D/file1.txt",
    "A/B/C/D/file2.txt",
    "A/B/D/E/file2.txt",
    "A/B/D/G/file3.txt",
    "A/B/D/G/file4.txt",
]);

// Print tree step by step
for(const nodePath of printPathsTree(tree)) {
    console.log(nodePath);
}

// Print with A as root
console.log("\nA AS ROOT:");
for(const nodePath of printPathsTree(tree.childs.values().next().value)) {
// for(const nodePath of printPathsTree(tree.childs.get("A"))) { // same
    console.log(nodePath);
}

Output:

WITH ROOT:
*/A/B
    |---C/D
          |---file1.txt
          |---file2.txt
    |---D
        |---E
            |---file2.txt
        |---G
            |---file3.txt
            |---file4.txt

A AS ROOT:
A/B
  |---C/D
        |---file1.txt
        |---file2.txt
  |---D
      |---E
          |---file2.txt
      |---G
          |---file3.txt
          |---file4.txt

You can build the tree by recursively grouping on the leading path name, and then merging parent-child names if a parent only has one child:

 var paths = ["A/B/C/D/file1.txt", "A/B/C/D/file2.txt", "A/B/D/E/file2.txt", "A/B/D/G/file3.txt", "A/B/D/G/file4.txt"] function merge_paths(paths){ var d = {}; var new_d = {} for (var [a, ...b] of paths){ d[a] = (a in d) ? [...d[a], b] : [b] } for (var i of Object.keys(d)){ if (d[i].every(x => x.length === 1)){ new_d[i] = d[i].map(x => x[0]); } else{ var k = merge_paths(d[i]) if (Object.keys(k).length > 1){ new_d[i] = k } else{ new_d[`${i}/${Object.keys(k)[0]}`] = k[Object.keys(k)[0]] } } } return new_d; } var result = merge_paths(paths.map(x => x.split('/'))) console.log(result)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM