我正在处理一个问题,在给定一组文件路径的情况下,我想打印文件结构。例如,对于给定的数组["/a/b/c", "a/a/a", "/a/b/d"]
,理想的结构看起来像:
a
b
c
d
a
a
但我的结构最终看起来更像这样:
a
b
c
a
a
a
b
据我所知,这是由于我的树没有识别树中何时已经存在节点造成的。因此,它添加了节点";a";三次,而不是认识到;a";已经存在并正在遍历。
let paths = ["/a/b/c", "a/a/a", "/a/b/d"]
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(element) {
this.children.push(element)
}
}
const head = new TreeNode('Head');
let cur = head;
paths.forEach(element => {
cur = head;
let filePath = element.split('/');
filePath.shift();
filePath.forEach(element => {
let newNode = new TreeNode(element);
if(!cur.children.includes(newNode)) {
cur.addChild(newNode);
cur = cur.children[cur.children.length - 1];
} else {
cur = cur.children.indexOf(newNode);
}
})
})
var spaceAppend = function(num) {
let i = 0;
let space = "";
while(i < num) {
space += " ";
i++;
}
return space;
}
var traverse = function(node, level = 0){
if(node === null)
return;
console.log(spaceAppend(level), node.value)
if(node.children) {
for(const n of node.children) {
traverse(n, level + 1);
}
}
}
traverse(head)
我的树实现有问题吗?
一些问题:
.includes()
不是找到匹配值的正确方法。请改用.find()
.indexOf()
将返回一个索引,因此这不是您想要在else
块中分配给cur
的正确值- 当CCD_ 7不以CCD_ 8开始时。您可以使用
.match()
而不是.split()
来简化处理,这样您就可以准确地获得路径的非空部分
问题不大:
- 没有必要在外循环之外定义
cur
- JavaScript有一个类似
spaceAppend
的原生函数。您可以使用.repeat()
- 当您实际上不需要
new TreeNode(element)
时,也会调用它。只有当您知道没有匹配的节点时,才能创建一个新节点 - 您可以用
.reduce()
替换内部的.forEach()
循环,这为cur
变量提供了更好的范围处理
这是您的代码,其中考虑了这些备注:
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(element) {
this.children.push(element);
}
}
let paths = ["/a/b/c", "a/a/a", "/a/b/d"];
const head = new TreeNode('Head');
paths.forEach(element => {
// Use .match() to only get non-empty elements
let filePath = element.match(/[^/]+/g);
filePath.reduce((cur, element) => {
// Use .find() instead of .includes()
let node = cur.children.find(child => child.value === element);
// Only create the node when needed:
if (!node) {
node = new TreeNode(element);
cur.addChild(node);
}
// Walk down one step in the tree
return node; // ...becomes the value of `cur`
}, head); // Initial value of reduction
});
const traverse = function(node, level=0) {
if (node === null) return;
// Use .repeat():
console.log(" ".repeat(level), node.value);
if (node.children) {
for (const n of node.children) {
traverse(n, level + 1);
}
}
}
traverse(head);
启动器阵列是["/a/b/c", "/a/a/a", "/a/b/d"]
("/a/a/a"
而不是("a/a/a")
?
我认为你遇到的问题的关键是线路
if(!cur.children.includes(newNode)) { ... }
创建新节点时,即使它具有与上一个相同的value
,在比较两个TreeNode
对象时也不会导致公平。您需要比较节点的value
,而不是节点本身。
因此,有一个简化版本的节点对象示例:
class TreeNode {
constructor(value) {
this.value = value;
}
}
a1 = new TreeNode('a');
a2 = new TreeNode('a');
console.log("a1 == a2");
console.log(a1 == a2); // false
console.log("a1.value == a2.value");
console.log(a1.value == a2.value); // true
我用一个比较value
s而不是TreeNode
对象的循环调整了内部forEach循环
filePath.forEach(element => {
let newNode = new TreeNode(element);
let tempNode = null;
for (var i = 0; i < cur.children.length; i++) {
if (cur.children[i].value == newNode.value) {
tempNode = cur.children[i];
}
}
if (tempNode == null) {
cur.addChild(newNode);
cur = newNode;
} else {
cur = tempNode;
}
});
代码笔上的完整代码片段
javascript中的对象平等处理起来不是特别好。有关更多信息,请参阅其他答案
这里有一个使用lodash和对象树化的解决方案。虽然它是更简单的代码,但引入额外的依赖关系显然是有代价的。
该解决方案首先将路径转换为树结构,然后使用对象树化进行可视化
// const lodash = require('lodash');
// const objectTreeify = require('object-treeify');
const myPaths = ['/a/b/c', 'a/a/a', '/a/b/d'];
const treeify = (paths) => objectTreeify(paths.reduce((p, c) => {
lodash.set(p, c.match(/[^/]+/g));
return p;
}, {}), {
spacerNoNeighbour: ' ',
spacerNeighbour: ' ',
keyNoNeighbour: '',
keyNeighbour: ''
});
console.log(treeify(myPaths));
/* =>
a
b
c
d
a
a
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/lodash@4.17.20"></script>
<script src="https://bundle.run/object-treeify@1.1.31"></script>
免责声明:我是object treeify 的作者