【问题标题】: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 处理额外的条件,拆分这些要求可能更容易;)
  • 很遗憾,我不能。

标签: javascript java algorithm tree


【解决方案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)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-10-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-02-05
          • 1970-01-01
          • 2020-11-14
          相关资源
          最近更新 更多