从文件路径构建树形结构

6
我将尝试创建一个树形视图,该视图基于文件路径,可以动态添加和删除。例如:
A/B/C/D/file1.txt
A/B/D/E/file2.txt
A/B/D/G/file3.txt
A/B/D/G/file4.txt

然而,我的树有一个要求,即没有子项(文件)的路径应该折叠成一个节点。对于上面的路径,它会产生以下结果:

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

您有什么想法?创建树很容易,但是我无法通过额外的条件...我认为我需要在添加项目并在发现某个路径具有更多子项时断开路径时使用某种递归(然后递归地执行相同的操作?)。 我应该使用某种trie吗? 当相同的路径可以有多个文件时是否有效?...谢谢!


我认为你应该按照正常的方式构建树形结构,让你的用户界面处理额外的条件,这样可能更容易分离这些要求 ;) - Icepickle
很遗憾,我不能。 - John Smith
4个回答

0

根据您的要求,似乎将新文件添加到C盘不会涉及递归操作。

如果您将file5.txt添加到文件夹C中,则必须将C/D转换为一个节点C,该节点具有2个子节点:file5.txt和一个名为D的新节点。 D将具有与旧节点C/D相同的子节点。然后,您可以删除节点C/D

但是,这不会影响节点A/B,因为文件夹A仍将只有一个文件夹(B)作为子文件夹。因此,您可以通过仅进行本地更改来解决问题。


0

我使用Map创建了一个具有自定义树节点格式的示例代码,并且打印函数是一个生成器函数,可逐行获取树的路径。

// 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);
}

输出:

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

0

您可以通过递归地根据前导路径名进行分组来构建树,并在父项只有一个子项时合并父子名称:

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)


0

让我们从一个简单的解决方案开始,将树打印为实际形态:

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

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

现在,让我们修改它以“缩短”单文件夹路径:

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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接