【问题标题】:Recursion in JavaScript to search in heavy JSONJavaScript 中的递归以在大量 JSON 中搜索
【发布时间】:2017-11-04 07:49:01
【问题描述】:

我正面临一个算法概念问题。使用 JavaScript 语言,我有一个大约 11 000 行的重型 JSON 对象,这是 HTML 文件转换的结果。 JSON的结构类似于DOM的结构,也就是说一个Object可以有一个属性children,一个由其他类似Object组成的数据结构。目标是在 JSON 中搜索并提取具有该属性的 Object 的属性 itemprop 的信息。 itemprop 属性位于 attributes 属性中的 Object 中,其中一些第一次提到的 Object 具有。

对象结构

{ type: 'x',
  tagName: 'y',
  attributes: { "itemprop" : "valueWanted" },
  children:
   [ Object, Object, Object] 
}

我想到了一个递归算法来解决。不幸的是,我不熟悉递归,下一个代码不起作用。

递归算法

var searchAttributesRecursive = function(children) {
    for (var i = 0; i < children.length; ++i) {
      if (children[i].hasOwnProperty('children')) {
        return searchAttributesRecursive(children[i].children);
      }
      else {
        if (children[i].hasOwnProperty('attributes')) {
            if (children[i].attributes.itemprop === "valueWanted") {
              console.log('success')
            }

          }
        }
        return; // probably a problem that breaks the loop
      }
    };

searchAttributesRecursive(startingChildren);

也许还有另一种更有效的通用算法来完成这项任务。我愿意接受建议。

更新

感谢您提供的所有解决方案和解释。更具体地说,看看@ChrisG 的简单解决方案。现在,我想在算法中添加一个特殊条件。

如果我想从下一个对象中检索数据,在对象具有wantedValue2 的子对象范围之外,您知道如何访问这些数据吗?该算法会有一个特殊情况,它满足wantedValue2,并且不想直接提取itemprop的数据。

对象结构特例

{
 "type": "",
  "tagName": "",
  "attributes": {
  "itemprop": "wantedValue"
   },
  "children": [{
      "type": "",
      "content": ""
      }
    ]
  },
 {
  "type": "",
  "content": ""
  }]
  },         
   {
  "type": "",
  "tagName": "",
  "attributes": {},
  "children": [
  {
   "type": "",
    "content": "here"
   }
  ]

【问题讨论】:

  • 搜索JSON,真的吗?它看起来像一个对象,你正在处理。请添加结构,至少少量,它的结构。
  • 你真的在寻找字符串“itemprop”吗?
  • @NinaScholz JSON 对象被转换为 JS 对象,不是吗?我添加了对象结构。一个 Object 可以有 attributes 或 children 属性。
  • @epascarello 是的。
  • 正如 Nina 所链接的,JSON 只是一种文本格式。这是一个字符串。除了 JSON 内置 JavaScript 对象之外,没有“JSON 对象”之类的东西。但是是的,JSON.parse 确实将 JSON 字符串转换为 JavaScript 对象。这是一个很小但很重要的区别。

标签: javascript json algorithm recursion bigdata


【解决方案1】:

您的返回将打破循环。如果确实返回,您只想返回:

var searchAttributesRecursive = function(children) {
    for (var i = 0; i < children.length; ++i) {
        if (children[i].hasOwnProperty('children')) {
            var result=searchAttributesRecursive(children[i].children);
            if(result) return result;//if weve found sth, return
        }

        if (children[i].hasOwnProperty('attributes')) {
            if (children[i].attributes.itemprop === "valueWanted1") {
              console.log('success')
              return children[i];//return sth useful
            }

       }
  }
 return false;//nothing found in this and in all childs
};

var elem=searchAttributesRecursive(startingChildren);

这将返回 第一个 找到的孩子。您可能想要返回一个数组:

var searchAttributesRecursive = function(children,result=[]) {
    for (var i = 0; i < children.length; ++i) {
        if (children[i].hasOwnProperty('children')) {
            searchAttributesRecursive(children[i].children,result);
        }
        if (children[i].hasOwnProperty('attributes')) {
            if (children[i].attributes.itemprop === "valueWanted1") {
              console.log('success')
              result.push(children[i]);//return sth useful
            }

       }
  }
 return result;//return all results found
};

var arr=searchAttributesRecursive(allElems);
arr.forEach(console.log);

通过将数组作为可选参数传递,可以快速轻松地将多棵树的遍历存储在一个结果中:

var arr=[];
searchAttributesRecursive(allElems,arr);
searchAttributesRecursive(allElemsTwo,arr);

【讨论】:

  • 重命名children 参数(不是属性)可能有助于缓解一些amazingcode12 对递归的困惑。 children[i].children == 令人困惑,current[i].children == 不太清楚。
  • @TheJim01 我认为这对 OP 来说是一个很好的建议。但是,我希望他的代码看起来相似,以便更容易理解......
  • @Jonasw 看起来属性未定义(TypeError: Cannot read property 'itemprop' of undefined)
  • @amazingcode12 那是不可能的。之前检查过吗?
  • @Jonasw 谢谢,它现在可以工作了,我忘了硬编码 valueWanted1。你有一点回报的结果。应该是返回结果。
【解决方案2】:

这是一个较短的版本:

注意函数需要一个数组,所以如果你的对象不是数组,你必须使用findItemprop([dom], "wanted")

function findItemprop(data, value, found) {
  if (!found) found = [];
  data.forEach((node) => {
    if (node.attributes && node.attributes.itemprop == value)
      found.push(node);
    if (node.children) findItemprop(node.children, value, found);
  });
  return found;
}

var dom = [{
  tag: "root",
  children: [{
    tag: "header",
    children: [{
      tag: "div"
    }]
  }, {
    tag: "div",
    id: "main",
    children: [{
      tag: "p",
      attributes: {
        itemprop: "wanted"
      }
    }]
  }, {
    tag: "footer",
    children: [{
      tag: "span",
      content: "copyright 2017",
      attributes: {
        itemprop: "wanted"
      }
    }]
  }]
}];

console.log(findItemprop(dom, "wanted"));

【讨论】:

  • 如果我想从下一个对象中检索数据,在对象具有 WantValue2 的子对象范围之外,您知道如何访问这些数据吗?该算法会有一个特殊情况,它满足wantedValue2,并且不想直接提取itemprop的数据。
  • @amazingcode12 我的答案底部的解决方案正是这样做的。它是通用的,因此它可以在任何嵌套属性下查找任何值。你应该试一试。
【解决方案3】:

您可以使用.some() 函数来执行此操作。它的作用是,如果任何迭代返回 true,它将返回 true,否则返回 false。因此,对于当前对象中的每个键,您将检查属性是否为 === 'attributes',如果是,您将检查 itemprop 属性以获得所需的值。如果当前键不是“属性”,而是=== 'children',它将递归并以相同的方式检查每个子对象。

var searchAttributesRecursive = function(obj, valueWanted) {
  if (obj instanceof Object) {
    if (obj.attributes && obj.attributes.itemprop === valueWanted) {
      return true;
    }
    if (obj.children) {
      return obj.children.some(function(_obj) {
        return searchAttributesRecursive(_obj, valueWanted);
      });
    } else {
      return false;
    }
  } else {
    return false;
  }
};
var obj = {
  type: 'x',
  tagName: 'y',
  attributes: {
    "itemprop": "wantedValue0"
  },
  children: [{
      type: 'x',
      tagName: 'y',
      attributes: {
        "itemprop": "wantedValue1"
      },
      children: []
    },
    {
      type: 'x',
      tagName: 'y',
      attributes: {
        "itemprop": "wantedValue2"
      },
      children: [{
        type: 'x',
        tagName: 'y',
        attributes: {
          "itemprop": "wantedValue3"
        },
        children: []
      }]
    }
  ]
};

console.log("Found 'wantedValue0': " + searchAttributesRecursive(obj, "wantedValue0"));
console.log("Found 'wantedValue1': " + searchAttributesRecursive(obj, "wantedValue1"));
console.log("Found 'wantedValue2': " + searchAttributesRecursive(obj, "wantedValue2"));
console.log("Found 'wantedValue3': " + searchAttributesRecursive(obj, "wantedValue3"));
console.log("Found 'wantedValue4': " + searchAttributesRecursive(obj, "wantedValue4"));

编辑 - 要使其动态工作并在任何嵌套属性或嵌套子属性中搜索 itemprop === wantedValue,您可以执行以下操作:

var searchAttributesRecursive2 = function(data, valueWanted) {
  if (Array.isArray(data)) {
    return data.some(function(elem) {
      return searchAttributesRecursive2(elem, valueWanted);
    });
  } else if (data instanceof Object) {
    return Object.keys(data).some(function(key) {
      var prop = data[key];
      return prop.itemprop === valueWanted || searchAttributesRecursive2(prop, valueWanted);
    });
  } else {
    return false;
  }
};

var obj = {
  type: 'x',
  tagName: 'y',
  attributes: {
    "itemprop": "wantedValue0"
  },
  children: [{
      type: 'x',
      tagName: 'y',
      attributes: {
        "itemprop": "wantedValue1"
      },
      children: []
    },
    {
      type: 'x',
      tagName: 'y',
      attributes: {
        "itemprop": "wantedValue2"
      },
      children: [{
        type: 'x',
        tagName: 'y',
        attributes: {
          "itemprop": "wantedValue3"
        },
        children: []
      }]
    }
  ]
};

console.log("Found 'wantedValue0': " + searchAttributesRecursive2(obj, "wantedValue0"));
console.log("Found 'wantedValue1': " + searchAttributesRecursive2(obj, "wantedValue1"));
console.log("Found 'wantedValue2': " + searchAttributesRecursive2(obj, "wantedValue2"));
console.log("Found 'wantedValue3': " + searchAttributesRecursive2(obj, "wantedValue3"));
console.log("Found 'wantedValue4': " + searchAttributesRecursive2(obj, "wantedValue4"));

【讨论】:

  • 但是循环遍历所有键是非常低效的不是吗?
  • @mhodges 感谢您对递归的解释。不幸的是,提供的代码不起作用。
  • @amazingcode12 您是否传入了想要的值?这是动态的,它不是硬编码的。
  • @Jonasw 是的,我最初将其设为通用,因此它会在任何属性下查找 .itemprop === valueWanted,但后来意识到 OP 只想专门查看 attributeschildren.attributes特性。我会更新我的帖子
  • @mhodges 是的,是动态传递的。
【解决方案4】:

感谢 Jonas w 他们的回答,我只是在添加标签以帮助纠正围绕递归的一些困惑,并希望使其更易于理解和使用。

首先,您传入子数组。这很好,但是当你检查它们时,你必须从它的数组索引中访问每一个。我的建议是让您的函数一次只处理一项。 (我打算用Jonas w的收集节点的方法,因为可能有不止一个节点有这个属性。我还要加上属性名作为参数,让它更动态一点。)

function searchAttributesRecursive(currentNode, parameterName, results=[]){
}

现在您可以一次只专注于一个节点。一旦它通过了检查,你就可以转移到孩子们身上了。

function searchAttributesRecursive(currentNode, parameterName, results=[]){
    if(currentNode.attributes && currentNode.attributes[parameterName]){
        results.push(currentNode);
    }
    if(currentNode.children){
        for(var i = 0, len = currentNode.children.length; i < len; ++i){
            searchAttributesRecursive(currentNode.children[i], parameterName, results);
        }
    }
}

这样称呼它:

var results = [];
searchAttributesRecursive(yourJsObject, "itemprop", results);

...用包含“itemprop”属性的节点填充results。您现在可以使用简单的循环打印属性值:

for(var i = 0, len = results.length; i < len; ++i){
    console.log(i, results[i].attributes.itemprop);
}

【讨论】:

  • 感谢您的回答。您在第一个条件(一个')'中有一个小错字太多并且代码不起作用:TypeError:无法读取未定义的属性'长度'。看起来 currentNode.children 没有定义。
  • @amazingcode12 好收获! :) 我已经编辑修复它。
猜你喜欢
  • 2014-04-08
  • 2011-10-02
  • 1970-01-01
  • 2022-08-10
  • 2017-05-10
  • 2020-04-30
  • 2011-08-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多