【问题标题】:Javascript object recursion to find an item at the deepest levelJavascript对象递归以在最深级别查找项目
【发布时间】:2017-08-02 21:39:06
【问题描述】:

我正在尝试递归搜索一个包含字符串、数组和其他对象的对象,以在最深层次上找到一个项目(匹配一个值),但是我总是得到未定义的返回结果。我可以通过一些控制台日志看到我正在找到该项目,但它被覆盖了。知道我哪里出错了吗?

  var theCobWeb = {
    biggestWeb: {
      item: "comb",
      biggerWeb: {
        items: ["glasses", "paperclip", "bubblegum"],
        smallerWeb: {
          item: "toothbrush",
          tinyWeb: {
            items: ["toenails", "lint", "wrapper", "homework"]
          }
        }
      },
      otherBigWeb: {
        item: "headphones"
      }
    }
  };

  function findItem (item, obj) {
  var foundItem;
  for (var key in obj) {
    if (obj[key] === item) {
      foundItem = obj;
    } else if (Array.isArray(obj[key]) && obj[key].includes(item)) {
      foundItem = obj;
    } else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
      findItem(item, obj[key]);
    } 
  }
      return foundItem;
}

var foundIt = findItem('glasses', theCobWeb);
console.log('The item is here: ' + foundIt); // The item is here: undefined 

编辑:根据下面的反馈对代码进行了一些清理。

【问题讨论】:

    标签: javascript object search recursion


    【解决方案1】:
    function findItem (item, obj) {
      for (var key in obj) {
        if (obj[key] === item) { // if the item is a property of the object
          return obj;            // return the object and stop further searching
        } else if (Array.isArray(obj[key]) && obj[key].includes(item)) { // if the item is inside an array property of the object
          return obj;            // return the object and stop the search
        } else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { // if the property is another object
          var res = findItem(item, obj[key]); // get the result of the search in that sub object
          if(res) return res; // return the result if the search was successful, otherwise don't return and move on to the next property
        } 
      }
      return null; // return null or any default value you want if the search is unsuccessful (must be falsy to work)
    }
    

    注意 1: Array.isArrayArray.prototype.includes 已经返回布尔值,因此无需根据布尔值检查它们。

    注意 2: 您可以使用 NOT 运算符 (!) 翻转布尔值。

    注意3:您必须在找到结果后立即返回(如果找到),这样您就不会浪费时间寻找已有的东西。

    注意4:搜索的返回结果将是一个对象(如果找到),由于对象是通过引用而不是值传递的,因此更改该对象的属性将更改原始对象的属性对象也是。

    编辑:找到最深的物体:

    如果你想找到最深的对象,你必须遍历对象obj 中的每个对象和子对象,并且每次你必须存储对象及其深度(如果结果的深度是当然比以前的结果更大)。下面是一些 cmets 的代码(我使用了一个内部函数 _find,它实际上被所有对象调用):

    function findItem (item, obj) {
        var found = null;              // the result (initialized to the default return value null)
        var depth = -1;                // the depth of the current found element (initialized to -1 so any found element could beat this one) (matched elements will not be assigned to found unless they are deeper than this depth)
    
        function _find(obj, d) {       // a function that take an object (obj) and on which depth it is (d)
            for (var key in obj) {     // for each ...
                // first call _find on sub-objects (pass a depth of d + 1 as we are going to a one deep bellow)
                if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { 
                    _find(obj[key], d + 1);
                }
                // then check if this object actually contain the item (we still at the depth d)
                else if (obj[key] === item || (Array.isArray(obj[key]) && obj[key].includes(item))) {
                    // if we found something and the depth of this object is deeper than the previously found element
                    if(d > depth) {
                        depth = d;     // then assign the new depth
                        found = obj;   // and assign the new result
                    }
                }
            }
        }
        _find(obj, 0);                 // start the party by calling _find on the object obj passed to findItem with a depth of 0
    
        // at this point found is either the initial value (null) means nothing is found or it is an object (the deepest one)
        return found;
    }
    

    【讨论】:

    • 谢谢。您包含了一些关于如何清理我的代码的非常有用的注释。这并没有完全完成我正在寻找的东西。我的目标是找到最深层次的项目,而不是第一个实例。所以在这个例子中,如果 tinyWeb.items 中也存在眼镜,我希望它返回,而不是更大的Web。
    • @Phil 这似乎有点棘手,因为如果两个嵌套对象中有"glasses" 但深度不同,该函数应该返回其中的任何一个还是只返回深层呢?
    • 只是试图从最深层次返回匹配。这就是为什么我试图递归整个对象并在每次查找时覆盖 foundItem
    • @Phil 抱歉回复晚了!检查上面的 EDIT 部分!
    • 我们真的还在写for-in 循环吗? 真的
    【解决方案2】:

    “我正在尝试递归搜索一个包含字符串、数组和其他对象的对象,以在最深层次上找到一个项目(匹配一个值),但是我总是得到未定义的返回结果。 "

    var foundIt = findItem('glasses', theCobWeb);
    console.log('The item is here: ' + foundIt); // The item is here: undefined 
    

    “物品在这里……” – 在哪里?

    那么你到底想要什么作为返回值?完成后是否应该只说"glasses"?在我看来,这有点毫无意义——从根本上说,这并不比只返回truefalse 更好。

    我前段时间写了这个函数,因为我需要搜索一堆数据,但还要知道它具体匹配的位置。我现在可能会稍微修改一下(或者至少包括类型注释),但它按原样工作,所以你去吧。

    // helpers
    const keys = Object.keys
    const isObject = x=> Object(x) === x
    const isArray = Array.isArray
    const rest = ([x,...xs])=> xs
    
    // findDeep
    const findDeep = (f,x) => {
      let make = (x,ks)=> ({node: x, keys: ks || keys(x)})
      let processNode = (parents, path, {node, keys:[k,...ks]})=> {
        if (k === undefined)
          return loop(parents, rest(path))
        else if (isArray(node[k]) || isObject(node[k]))
          return loop([make(node[k]), make(node, ks), ...parents], [k, ...path])
        else if (f(node[k], k))
          return {parents, path: [k,...path], node}
        else
          return loop([{node, keys: ks}, ...parents], path)
      }
      let loop = ([node,...parents], path) => {
        if (node === undefined)
          return {parents: [], path: [], node: undefined}
        else
          return processNode(parents, path, node)
      }
      return loop([make(x)], [])
    }
    
    // your sample data
    var theCobWeb = {biggestWeb: {item: "comb",biggerWeb: {items: ["glasses", "paperclip", "bubblegum"],smallerWeb: {item: "toothbrush",tinyWeb: {items: ["toenails", "lint", "wrapper", "homework"]}}},otherBigWeb: {item: "headphones"}}};
    
    // find path returns {parents, path, node}
    let {path, node} = findDeep((value,key)=> value === "glasses", theCobWeb)
    
    // path to get to the item, note it is in reverse order
    console.log(path) // => [0, 'items', 'biggerWeb', 'biggestWeb']
    
    // entire matched node
    console.log(node) // => ['glasses', 'paperclip', 'bubblegum']

    这里的基本直觉是node[path[0]] === searchTerm


    匹配查询的完整路径

    我们得到匹配数据的整个关键路径。这很有用,因为我们可以根据搜索的根确切地知道它在哪里。要验证路径是否正确,请参阅此示例

    const lookup = ([p,...path], x) =>
      (p === undefined) ? x : lookup(path,x)[p]
    lookup([0, 'items', 'biggerWeb', 'biggestWeb'], theCobWeb) // => 'glasses'
    

    不匹配的查询

    请注意,如果我们搜索未找到的内容,node 将是 undefined

    let {path, node} = findDeep((value,key)=> value === "sonic the hog", theCobWeb)
    console.log(path) // => []
    console.log(node) // => undefined
    

    搜索特定的键/值对

    搜索函数接收valuekey 参数。随心所欲地使用它们

    let {path, node} = findDeep((value,key)=> key === 'item' && value === 'toothbrush', theCobWeb)
    console.log(path) // => [ 'item', 'smallerWeb', 'biggerWeb', 'biggestWeb' ]
    console.log(node) // => { item: 'toothbrush', tinyWeb: { items: [ 'toenails', 'lint', 'wrapper', 'homework' ] } }
    

    短路 - 150cc

    哦,因为我宠坏了你,findDeep 会在找到第一个匹配项后尽快返回。它不会浪费计算周期并在知道答案后继续遍历您的一堆数据。这是一件好事。


    去探索

    要有勇气,敢于冒险。上面的 findDeep 函数还为返回的对象提供了 parents 属性。它在某些方面可能对您有用,但解释起来有点复杂,对于回答问题并不是很重要。为了简化这个答案,我只提一下它。

    【讨论】:

    • tfw 当你编写一个不是玩具的核心递归过程时
    • 它实际上返回了对象引用。所以不像你想象的那么没用。感谢您包含其他代码,这很有趣。
    【解决方案3】:

    这是因为递归调用没有将返回值分配给变量。 你应该检查递归调用的返回值,如果为真则返回,如果后面有其他逻辑,则从 for 循环中中断。

     function findItem(item, obj) {
     for (var key in obj) {
         if (obj[key] === item) {
             return obj;
         } else if (Array.isArray(obj[key]) === true && obj[key].includes(item) === true) {
             return obj;
         } else if (typeof obj[key] === 'object' && Array.isArray(obj[key]) === false) {
             var foundItem = findItem(item, obj[key]);
             if(foundItem) 
              return foundItem;
         }
     }
    

    【讨论】:

    • 如果您查找 headphones 将无法正常工作 - return findItem 应该是有条件的
    • 类似 - foundItem = findItem(item, obj[key]); if (foundItem) { return foundItem; }
    • 没错,我已经更新了它也适用于它,(y)
    猜你喜欢
    • 2015-04-23
    • 2018-02-14
    • 2016-08-07
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    • 2012-09-06
    • 2016-06-14
    相关资源
    最近更新 更多