从文件路径构建树

Posted

技术标签:

【中文标题】从文件路径构建树【英文标题】:Build a tree from file paths 【发布时间】:2015-09-12 08:35:14 【问题描述】:

我正在尝试从文件路径创建一个树形视图,可以动态添加和删除它,例如:

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

有什么想法吗?创建树很容易,但我无法超越那个额外的条件......我假设我必须使用某种递归来添加项目并打破路径,因为我们发现某个路径有更多的孩子(然后递归地做同样的事情?)。我应该使用某种特里吗?当同一个路径可以有多个文件时它会工作吗?...谢谢!

【问题讨论】:

我认为你应该像往常一样构建树,让你的 UI 处理额外的条件,拆分这些要求可能更容易;) 很遗憾,我不能。 【参考方案1】:

让我们从一个简单的解决方案开始,按原样打印树:

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

【讨论】:

【参考方案2】:

鉴于您的要求,似乎将新文件添加到 C 中并不意味着递归操作。

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

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

【讨论】:

【参考方案3】:

我使用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

【讨论】:

【参考方案4】:

您可以通过在前导路径名称上递归分组来构建树,然后如果父母只有一个孩子,则合并父子名称:

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)

【讨论】:

以上是关于从文件路径构建树的主要内容,如果未能解决你的问题,请参考以下文章

从 PHP 中的平面路径数组构建目录树

从路径列表中表示文件系统(文件/目录)的 Java 树

从列表 os 文件路径 (Python) 构造树 - 取决于性能

将文件从构建文件夹(构建后)移动到具有相对路径的不同文件夹 - Webpack

导出/导入 Eclipse 构建路径

如何从构建的 Vue 项目中更改路径结构?