如何使用 JavaScript 在树中查找节点
How to find a node in a tree with JavaScript
我有和对象文字,本质上是一棵没有固定级别数的树。我怎样才能在树中搜索一个特定的节点,然后在javascript中以有效的方式找到该节点时返回该节点?
本质上,我有一棵这样的树,并希望找到标题为"randomNode_1"的节点
var data = [
{
title: 'topNode',
children: [
{
title: 'node1',
children: [
{
title: 'randomNode_1'
},
{
title: 'node2',
children: [
{
title: 'randomNode_2',
children:[
{
title: 'node2',
children: [
{
title: 'randomNode_3',
}]
}
]
}]
}]
}
]
}];
这个答案基于@Ravindra的答案,但具有真正的递归。
function searchTree(element, matchingTitle){
if(element.title == matchingTitle){
return element;
}else if (element.children != null){
var i;
var result = null;
for(i=0; result == null && i < element.children.length; i++){
result = searchTree(element.children[i], matchingTitle);
}
return result;
}
return null;
}
然后你可以称之为:
var element = data[0];
var result = searchTree(element, 'randomNode_1');
这是一个迭代解决方案:
var stack = [], node, ii;
stack.push(root);
while (stack.length > 0) {
node = stack.pop();
if (node.title == 'randomNode_1') {
// Found it!
return node;
} else if (node.children && node.children.length) {
for (ii = 0; ii < node.children.length; ii += 1) {
stack.push(node.children[ii]);
}
}
}
// Didn't find it. Return null.
return null;
这是一个使用 Stack 方法的迭代函数,灵感来自 FishBasketGordo 的答案,但利用了一些 ES2015 语法来缩短内容。
由于这个问题已经被查看了很多次,我决定更新我的答案,以提供一个带有参数的函数,使其更加灵活:
function search (tree, value, key = 'id', reverse = false) {
const stack = [ tree[0] ]
while (stack.length) {
const node = stack[reverse ? 'pop' : 'shift']()
if (node[key] === value) return node
node.children && stack.push(...node.children)
}
return null
}
这样,现在可以将数据传递tree
本身、要搜索的所需value
以及可以具有所需值的属性key
:
search(data, 'randomNode_2', 'title')
最后,我最初的答案使用了Array.pop
,这会导致在多个匹配的情况下匹配最后一项。事实上,这可能真的很令人困惑。受到Superole评论的启发,我现在使用它Array.shift
,所以先进先出的行为是默认的。
如果您真的想要旧的后进先出行为,我提供了一个额外的参数reverse
:
search(data, 'randomNode_2', 'title', true)
我的回答灵感来自FishBasketGordo的迭代答案。它稍微复杂一些,但也更灵活,您可以拥有多个根节点。
/**searchs through all arrays of the tree if the for a value from a property
* @param aTree : the tree array
* @param fCompair : This function will receive each node. It's upon you to define which
condition is necessary for the match. It must return true if the condition is matched. Example:
function(oNode){ if(oNode["Name"] === "AA") return true; }
* @param bGreedy? : us true to do not stop after the first match, default is false
* @return an array with references to the nodes for which fCompair was true; In case no node was found an empty array
* will be returned
*/
var _searchTree = function(aTree, fCompair, bGreedy){
var aInnerTree = []; // will contain the inner children
var oNode; // always the current node
var aReturnNodes = []; // the nodes array which will returned
// 1. loop through all root nodes so we don't touch the tree structure
for(keysTree in aTree) {
aInnerTree.push(aTree[keysTree]);
}
while(aInnerTree.length > 0) {
oNode = aInnerTree.pop();
// check current node
if( fCompair(oNode) ){
aReturnNodes.push(oNode);
if(!bGreedy){
return aReturnNodes;
}
} else { // if (node.children && node.children.length) {
// find other objects, 1. check all properties of the node if they are arrays
for(keysNode in oNode){
// true if the property is an array
if(oNode[keysNode] instanceof Array){
// 2. push all array object to aInnerTree to search in those later
for (var i = 0; i < oNode[keysNode].length; i++) {
aInnerTree.push(oNode[keysNode][i]);
}
}
}
}
}
return aReturnNodes; // someone was greedy
}
最后,您可以像这样使用该函数:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, false);
console.log("Node with title found: ");
console.log(foundNodes[0]);
如果你想找到所有具有这个标题的节点,你可以简单地切换bGreedy参数:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, true);
console.log("NodeS with title found: ");
console.log(foundNodes);
在树中查找节点:
假设我们有一棵树,
比如let tree = [{
id: 1,
name: 'parent',
children: [
{
id: 2,
name: 'child_1'
},
{
id: 3,
name: 'child_2',
children: [
{
id: '4',
name: 'child_2_1',
children: []
},
{
id: '5',
name: 'child_2_2',
children: []
}
]
}
]
}];
function findNodeById(tree, id) {
let result = null
if (tree.id === id) {
return tree;
}
if (Array.isArray(tree.children) && tree.children.length > 0) {
tree.children.some((node) => {
result = findNodeById(node, id);
return result;
});
}
return result;}
你必须使用递归。
var currChild = data[0];
function searchTree(currChild, searchString){
if(currChild.title == searchString){
return currChild;
}else if (currChild.children != null){
for(i=0; i < currChild.children.length; i ++){
if (currChild.children[i].title ==searchString){
return currChild.children[i];
}else{
searchTree(currChild.children[i], searchString);
}
}
return null;
}
return null;
}
ES6+:
const deepSearch = (data, value, key = 'title', sub = 'children', tempObj = {}) => {
if (value && data) {
data.find((node) => {
if (node[key] == value) {
tempObj.found = node;
return node;
}
return deepSearch(node[sub], value, key, sub, tempObj);
});
if (tempObj.found) {
return tempObj.found;
}
}
return false;
};
const result = deepSearch(data, 'randomNode_1', 'title', 'children');
此函数是通用的,并且以递归方式进行搜索。如果输入树是对象(单根(还是对象数组(许多根对象(,这并不重要。您可以配置在树对象中保存子数组的 prop 名称。
// Searches items tree for object with specified prop with value
//
// @param {object} tree nodes tree with children items in nodesProp[] table, with one (object) or many (array of objects) roots
// @param {string} propNodes name of prop that holds child nodes array
// @param {string} prop name of searched node's prop
// @param {mixed} value value of searched node's prop
// @returns {object/null} returns first object that match supplied arguments (prop: value) or null if no matching object was found
function searchTree(tree, nodesProp, prop, value) {
var i, f = null; // iterator, found node
if (Array.isArray(tree)) { // if entry object is array objects, check each object
for (i = 0; i < tree.length; i++) {
f = searchTree(tree[i], nodesProp, prop, value);
if (f) { // if found matching object, return it.
return f;
}
}
} else if (typeof tree === 'object') { // standard tree node (one root)
if (tree[prop] !== undefined && tree[prop] === value) {
return tree; // found matching node
}
}
if (tree[nodesProp] !== undefined && tree[nodesProp].length > 0) { // if this is not maching node, search nodes, children (if prop exist and it is not empty)
return searchTree(tree[nodesProp], nodesProp, prop, value);
} else {
return null; // node does not match and it neither have children
}
}
我在本地测试了它,它工作正常,但它不知何故无法在 jsfiddle 或 jsbin 上运行......(这些网站上的重复出现问题??
运行代码:
var data = [{
title: 'topNode',
children: [{
title: 'node1',
children: [{
title: 'randomNode_1'
}, {
title: 'node2',
children: [{
title: 'randomNode_2',
children: [{
title: 'node2',
children: [{
title: 'randomNode_3',
}]
}]
}]
}]
}]
}];
var r = searchTree(data, 'children', 'title', 'randomNode_1');
//var r = searchTree(data, 'children', 'title', 'node2'); // check it too
console.log(r);
它可以在 http://www.pythontutor.com/live.html#mode=edit 中工作(粘贴代码(
no BS 版本:
const find = (root, title) =>
root.title === title ?
root :
root.children?.reduce((result, n) => result || find(n, title), undefined)
用法:
const data = {
title: "root",
children: [
{ title: "ch1" },
{ title: "ch2", children: [{ title: "ch21" }] }
]
}
find(data, 'ch21') // returns { title: 'ch21' }
这是基本的递归问题。
window.parser = function(searchParam, data) {
if(data.title != searchParam) {
returnData = window.parser(searchParam, children)
} else {
returnData = data;
}
return returnData;
}
这是一个更复杂的选项 - 它找到树状节点中的第一项,并提供(node,nodeChildrenKey,键/值对和可选的附加键/值对(
const findInTree = (node, childrenKey, key, value, additionalKey?, additionalValue?) => {
let found = null;
if (additionalKey && additionalValue) {
found = node[childrenKey].find(x => x[key] === value && x[additionalKey] === additionalValue);
} else {
found = node[childrenKey].find(x => x[key] === value);
}
if (typeof(found) === 'undefined') {
for (const item of node[childrenKey]) {
if (typeof(found) === 'undefined' && item[childrenKey] && item[childrenKey].length > 0) {
found = findInTree(item, childrenKey, key, value, additionalKey, additionalValue);
}
}
}
return found;
};
export { findInTree };
希望它对某人有所帮助。
在 2022 年使用 TypeScript 和 ES5
只需使用基本的重新创建和内置数组方法来循环数组。不要使用 Array.find()
,因为这会返回错误的节点。请改用Array.some()
,这样可以打破循环。
interface iTree {
id: string;
children?: iTree[];
}
function findTreeNode(tree: iTree, id: string) {
let result: iTree | null = null;
if (tree.id === id) {
result = tree;
} else if (tree.children) {
tree.children.some((node) => {
result = findTreeNode(node, id);
return result; // break loop
});
}
return result;
}
一个灵活的递归解决方案,适用于任何树
// predicate: (item) => boolean
// getChildren: (item) => treeNode[]
searchTree(predicate, getChildren, treeNode) {
function search(treeNode) {
if (!treeNode) {
return undefined;
}
for (let treeItem of treeNode) {
if (predicate(treeItem)) {
return treeItem;
}
const foundItem = search(getChildren(treeItem));
if (foundItem) {
return foundItem;
}
}
}
return search(treeNode);
}
在树中找到元素的所有父元素
let objects = [{
id: 'A',
name: 'ObjA',
children: [
{
id: 'A1',
name: 'ObjA1'
},
{
id: 'A2',
name: 'objA2',
children: [
{
id: 'A2-1',
name: 'objA2-1'
},
{
id: 'A2-2',
name: 'objA2-2'
}
]
}
]
},
{
id: 'B',
name: 'ObjB',
children: [
{
id: 'B1',
name: 'ObjB1'
}
]
}
];
let docs = [
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD2',
name: 'Typde Doc2'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx2',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A2',
name: 'docA2'
},
typedoc: {
id: 'TDx2',
name: 'Type de Doc x2'
}
},
{
object: {
id: 'A2-1',
name: 'docA2-1'
},
typedoc: {
id: 'TDx2-1',
name: 'Type de Docx2-1'
},
},
{
object: {
id: 'A2-2',
name: 'docA2-2'
},
typedoc: {
id: 'TDx2-2',
name: 'Type de Docx2-2'
},
},
{
object: {
id: 'B',
name: 'docB'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'B1',
name: 'docB1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
}
];
function buildAllParents(doc, objects) {
for (let o = 0; o < objects.length; o++) {
let allParents = [];
let getAllParents = (o, eleFinded) => {
if (o.id === doc.object.id) {
doc.allParents = allParents;
eleFinded = true;
return { doc, eleFinded };
}
if (o.children) {
allParents.push(o.id);
for (let c = 0; c < o.children.length; c++) {
let { eleFinded, doc } = getAllParents(o.children[c], eleFinded);
if (eleFinded) {
return { eleFinded, doc };
} else {
continue;
}
}
}
return { eleFinded };
};
if (objects[o].id === doc.object.id) {
doc.allParents = [objects[o].id];
return doc;
} else if (objects[o].children) {
allParents.push(objects[o].id);
for (let c = 0; c < objects[o].children.length; c++) {
let eleFinded = null;`enter code here`
let res = getAllParents(objects[o].children[c], eleFinded);
if (res.eleFinded) {
return res.doc;
} else {
continue;
}
}
}
}
}
docs = docs.map(d => buildAllParents(d, objects`enter code here`))
这是一个迭代广度优先搜索。它返回包含给定名称 (nodeName( 和给定值 (nodeValue( 的子节点的第一个节点。
getParentNode(nodeName, nodeValue, rootNode) {
const queue= [ rootNode ]
while (queue.length) {
const node = queue.shift()
if (node[nodeName] === nodeValue) {
return node
} else if (node instanceof Object) {
const children = Object.values(node)
if (children.length) {
queue.push(...children)
}
}
}
return null
}
它将像这样用于解决原始问题:
getParentNode('title', 'randomNode_1', data[0])
基于 "Erick Petrucelli" 的代码增强
- 删除"反向"选项
- 添加多根支持
- 添加用于控制"子项"可见性的选项
- 打字稿就绪
- 单元测试就绪
function searchTree(
tree: Record<string, any>[],
value: unknown,
key = 'value',
withChildren = false,
) {
let result = null;
if (!Array.isArray(tree)) return result;
for (let index = 0; index < tree.length; index += 1) {
const stack = [tree[index]];
while (stack.length) {
const node = stack.shift()!;
if (node[key] === value) {
result = node;
break;
}
if (node.children) {
stack.push(...node.children);
}
}
if (result) break;
}
if (withChildren !== true) {
delete result?.children;
}
return result;
}
测试可以在以下位置找到: https://gist.github.com/aspirantzhang/a369aba7f84f26d57818ddef7d108682
根据我的需要写了另一个
- 条件被注入。
- 找到的分支的路径可用
- 可以在条件语句中使用当前路径
- 可用于将树项映射到另一个对象
// if predicate returns true, the search is stopped
function traverse2(tree, predicate, path = "") {
if (predicate(tree, path)) return true;
for (const branch of tree.children ?? [])
if (traverse(branch, predicate, `${path ? path + "/" : ""}${branch.name}`))
return true;
}
例
let tree = {
name: "schools",
children: [
{
name: "farzanegan",
children: [
{
name: "classes",
children: [
{ name: "level1", children: [{ name: "A" }, { name: "B" }] },
{ name: "level2", children: [{ name: "C" }, { name: "D" }] },
],
},
],
},
{ name: "dastgheib", children: [{ name: "E" }, { name: "F" }] },
],
};
traverse(tree, (branch, path) => {
console.log("searching ", path);
if (branch.name === "C") {
console.log("found ", branch);
return true;
}
});
输出
searching
searching farzanegan
searching farzanegan/classes
searching farzanegan/classes/level1
searching farzanegan/classes/level1/A
searching farzanegan/classes/level1/B
searching farzanegan/classes/level2
searching farzanegan/classes/level2/C
found { name: 'C' }
const flattenTree = (data: any) => {
return _.reduce(
data,
(acc: any, item: any) => {
acc.push(item);
if (item.children) {
acc = acc.concat(flattenTree(item.children));
delete item.children;
}
return acc;
},
[]
);
};
将嵌套树转换为深度为 0 的对象的方法。我们可以在这样的对象中转换对象,并且可以更轻松地执行搜索。
以下内容在我这边工作:
function searchTree(data, value) {
if(data.title == value) {
return data;
}
if(data.children && data.children.length > 0) {
for(var i=0; i < data.children.length; i++) {
var node = traverseChildren(data.children[i], value);
if(node != null) {
return node;
}
}
}
return null;
}
- JavaScript选择器:查找包含具有特定属性的子体的节点
- jquery在元素中查找文本节点,并使用标记进行连接和包装
- 查找与锁定和更新调度相关的一个或两个节点模块
- 查找将 onclick 事件分配给节点的代码
- 在选定节点的有向、未加权图中查找所有路径
- 使用 XPath 查找节点后的文本节点
- 连接到节点服务器自动查找掩码
- 使用数据库查找在节点中导出大文件 - 避免多次数据库调用
- 如何使用 JavaScript 在 DOM 节点上查找事件侦听器
- 在节点列表中查找节点
- 如何在不依赖 javascript 中的 id、类或内容的情况下查找和检索节点位置
- 使用 jQuery 在 Ajax 结果中查找节点
- 在 el 中查找节点
- 在 json 树中按 id 查找节点
- 查找节点的事件处理程序
- 如何使用 JavaScript 在树中查找节点
- 通过一些参数在javascript树中查找节点
- Dojo查找节点下的节点
- 在JQuery中查找节点的成本是多少?
- 从jtree获取检查id - Get_Checked查找节点但不查找id