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.