簡體   English   中英

如何使用 JavaScript 在樹中查找節點

[英]How to find a node in a tree with JavaScript

我有 object 文字,它本質上是一棵沒有固定數量級別的樹。 我 go 如何在樹中搜索特定節點,然后在 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 ,所以先進先出行為是默認的。

如果你真的想要舊的后進先出行為,我提供了一個額外的 arg 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);

你必須使用遞歸。

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;
}

這個函數是通用的並且遞歸搜索。 如果輸入樹是對象(單根)或對象數組(許多根對象),這並不重要。 您可以配置在樹對象中保存子數組的道具名稱。

// 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 (粘貼代碼)

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

這是基本的遞歸問題。

window.parser = function(searchParam, data) {
  if(data.title != searchParam) {
    returnData = window.parser(searchParam, children)
  } else {
     returnData = data;
  }

  return returnData;
}

以下是我的工作:

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;

}

適用於任何樹的靈活遞歸解決方案

// 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`))

這是一個更復雜的選項 - 它通過提供(節點、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 };

希望它可以幫助某人。

這是一個迭代廣度優先搜索。 它返回包含給定名稱 (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 ”的代碼增強

  1. 刪除“反向”選項
  2. 添加多根支持
  3. 添加一個選項來控制“孩子”的可見性
  4. 打字稿准備好了
  5. 單元測試准備就緒
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

無 BS 版本:

const find = (root, title) => 
  root.title === title ?
    root :
    root.children?.reduce((result, n) => result || find(n, title), undefined)

根據我的需要寫了另一個

  1. 條件注入。
  2. 找到的分支路徑可用
  3. 當前路徑可用於條件語句
  4. 可用於將樹項映射到另一個對象
// if predicate returns true, the search is stopped
function traverse(tree, predicate, path = "") {
  if (predicate(tree, path)) return true;
  for (let i = 0; i < (tree.children?.length ?? 0); i++)
    if (traverse(tree.children[i], predicate, `${path ? path + "/" : ""}${tree.children[i].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' }

在樹中找到一個節點:

假設我們有一棵樹像

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;}

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; }

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 的 object 的方法。我們可以像這樣將 object 轉換為 object,並且可以更輕松地執行搜索。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM