我正在尝试返回JSON对象结构中的特定节点,该结构看起来像这样
{
"id":"0",
"children":[
{
"id":"1",
"children":[...]
},
{
"id":"2",
"children":[...]
}
]
}
所以这是一个像树一样的孩子 - 父母关系。每个节点都有一个唯一的ID。 我正在尝试找到像这样的节点
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
currentNode.children.forEach(function (currentChild) {
findNode(id, currentChild);
});
}
}
我通过findNode("10", rootNode)
执行搜索。但即使搜索找到匹配项,该函数也始终返回undefined
。我觉得递归函数在找到匹配后没有停止并且继续运行finally返回undefined
时感觉不好,因为在后面的递归执行中它没有到达返回点,但我不知道如何解决这个问题。
请帮忙!
答案 0 :(得分:34)
递归搜索时,必须通过返回结果传回结果。但是,您没有返回findNode(id, currentChild)
的结果。
function findNode(id, currentNode) {
var i,
currentChild,
result;
if (id == currentNode.id) {
return currentNode;
} else {
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
for (i = 0; i < currentNode.children.length; i += 1) {
currentChild = currentNode.children[i];
// Search in the current child
result = findNode(id, currentChild);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
答案 1 :(得分:4)
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
var result;
currentNode.children.forEach(function(node){
if(node.id == id){
result = node;
return;
}
});
return (result ? result : "No Node Found");
}
}
console.log(findNode("10", node));
如果节点列表中存在节点,则此方法将返回该节点。但是这将循环遍历节点的所有子节点,因为我们无法成功中断forEach
流。更好的实现方式如下所示。
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
var result;
for(var index in currentNode.children){
var node = currentNode.children[index];
if(node.id == id)
return node;
findNode(id, node);
}
return "No Node Present";
}
}
console.log(findNode("1", node));
答案 2 :(得分:3)
我使用以下
var searchObject = function (object, matchCallback, currentPath, result, searched) {
currentPath = currentPath || '';
result = result || [];
searched = searched || [];
if (searched.indexOf(object) !== -1 && object === Object(object)) {
return;
}
searched.push(object);
if (matchCallback(object)) {
result.push({path: currentPath, value: object});
}
try {
if (object === Object(object)) {
for (var property in object) {
if (property.indexOf("$") !== 0) {
//if (Object.prototype.hasOwnProperty.call(object, property)) {
searchObject(object[property], matchCallback, currentPath + "." + property, result, searched);
//}
}
}
}
}
catch (e) {
console.log(object);
throw e;
}
return result;
}
然后你可以写
searchObject(rootNode, function (value) { return value != null && value != undefined && value.id == '10'; });
现在这适用于循环引用,您可以通过更改matchCallback
函数来匹配您喜欢的任何字段或字段组合。
答案 3 :(得分:1)
既然这个老问题又被提出来了,这里有一个不同的方法。我们可以编写一个相当通用的 searchTree
函数,然后在 findId
函数中使用它。 searchTree
做遍历对象的工作;它接受回调以及树;回调确定节点是否匹配。与节点一样,回调函数还提供了两个函数 next
和 found
,我们分别调用它们,不带任何参数来表示我们应该继续或找到匹配项。如果未找到匹配项,则返回 null
。
看起来像这样:
const searchTree = (fn) => (obj) =>
Array.isArray(obj)
? obj.length == 0
? null
: searchTree (fn) (obj [0]) || searchTree (fn) (obj .slice (1))
: fn (
obj,
() => searchTree (fn) (obj .children || []),
() => obj
)
const findId = (target, obj) => searchTree (
(node, next, found) => node.id == target ? found () : next(),
) (tree)
const tree = {id: 1, name: 'foo', children: [
{id: 2, name: 'bar', children: []},
{id: 3, name: 'baz', children: [
{id: 17, name: 'qux', children: []},
{id: 42, name: 'corge', children: []},
{id: 99, name: 'grault', children: []}
]}
]}
console .log (findId (42, tree))
console .log (findId (57, tree))
此代码特定于在属性 children
下的数组中找到子节点的结构。虽然我们可以根据需要使其更通用,但我发现这是一个可以支持的通用结构。
有一个很好的论点,即使用相互递归编写会更好。如果我们愿意,我们可以获得与此版本相同的 API:
const searchArray = (fn) => ([x, ...xs]) =>
x === undefined
? null
: searchTree (fn) (x) || searchArray (fn) (xs)
const searchTree = (fn) => (obj) =>
fn (
obj,
() => searchArray (fn) (obj .children || []),
(x) => x
)
这也是一样的。但我发现代码更清晰。不过,两者都应该完成这项工作。
答案 4 :(得分:0)
我真的很喜欢树搜索!对于当今大多数复杂的结构化任务,树是极为常见的数据结构。所以我也有类似的午餐任务。我什至进行了深入研究,但还没有发现任何新东西!所以我今天为您准备的是“我如何用现代JS语法实现它”:
// helper
find_subid = (id, childArray) => {
for( child of childArray ) {
foundChild = find_id( i, child ); // not sub_id, but do a check (root/full search)!
if( foundChild ) // 200
return foundChild;
}
return null; // 404
}
// actual search method
find_id = (id, parent) => (id == parent.id) : parent : find_subid(id, parent.childArray);
答案 5 :(得分:0)
我会尽量不要重新发明轮子。我们将object-scan用于所有数据处理需求。从概念上讲,它很简单,但是可以容纳很多很酷的东西。这是解决特定问题的方法
const data = {
"id": "0",
"children": [{
"id": "1",
"children": [{
"id": "3",
"children": []
},
{
"id": "4",
"children": []
}
]
},
{
"id": "2",
"children": [{
"id": "5",
"children": []
},
{
"id": "6",
"children": []
}
]
}
]
};
const findNode = (id, input) => {
let obj = null;
objectScan(['**.id'], {
filterFn: (key, value, { parents }) => {
if (value === id) {
obj = parents[0];
}
},
breakFn: () => obj !== null
})(data);
return obj;
};
const result = findNode('6', data);
// result =>
{
"id": "6",
"children": []
}
答案 6 :(得分:0)
递归结构搜索,修改,键/值调整/替换。
const results = []; // to store the search results
mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
// do something cool with "v" (or key, or obj)
// return nothing (undefined) to keep the original value
// if we search:
if (key === 'name' && v === 'Roman'){
results.push(obj);
}
// more example flow:
if (isCircular) {
delete obj[key]; // optionally - we decide to remove circular links
} else if (v === 'Russia') {
return 'RU';
} else if (key.toLocaleLowerCase() === 'foo') {
return 'BAR';
} else if (key === 'bad_key') {
delete obj[key];
obj['good_key'] = v;
} else {
return v; // or undefined, same effect
}
});
您可以将其用作搜索回调,只需不返回任何内容(不会影响任何内容),然后为Array / Set / Map选择所需的值即可。
请注意,回调在每个叶子/值/键(不仅是对象)上运行。
或者您可以使用回调来调整特定值,甚至更改键。它还会自动检测循环,并提供标记供您决定如何处理它们。
(使用ES6)
函数本身+一些示例演示数据
function mapNodesRecursively(obj, mapCallback, { wereSet } = {}) {
if (!wereSet) {
wereSet = new Set();
}
if (obj && (obj === Object(obj) || Array.isArray(obj))) {
wereSet.add(obj);
for (let key in obj) {
if (!obj.hasOwnProperty(key)){
continue;
}
let v = obj[key];
const isCircular = wereSet.has(v);
const mapped = mapCallback({ v, key, obj, isCircular });
if (typeof (mapped) !== 'undefined') {
obj[key] = mapped;
v = mapped;
}
if (!isCircular) {
mapNodesRecursively(v, mapCallback, { wereSet });
}
}
}
return obj;
}
let obj = {
team: [
{
name: 'Roman',
country: 'Russia',
bad_key: 123,
},
{
name: 'Igor',
country: 'Ukraine',
FOO: 'what?',
},
{
someBool: true,
country: 'Russia',
},
123,
[
1,
{
country: 'Russia',
just: 'a nested thing',
a: [{
bad_key: [{
country: 'Russia',
foo: false,
}],
}],
},
],
],
};
// output the initial data
document.getElementById('jsInput').innerHTML = JSON.stringify(obj, null, 2);
// adding some circular link (to fix with our callback)
obj.team[1].loop = obj;
mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
if (isCircular) {
delete obj[key]; // optionally - we decide to remove circular links
} else if (v === 'Russia') {
return 'RU';
} else if (key.toLocaleLowerCase() === 'foo') {
return 'BAR';
} else if (key === 'bad_key') {
delete obj[key];
obj['good_key'] = v;
} else {
return v;
}
});
// output the result - processed object
document.getElementById('jsOutput').innerHTML = JSON.stringify(obj, null, 2);
.col {
display: inline-block;
width: 40%;
}
<div>
<h3>Recursive structure modification, keys/values adjustments/replacement</h3>
<ol>
<li>
Replacing "Russia" values with "RU"
</li>
<li>
Setting the value "BAR" for keys "FOO"
</li>
<li>
Changing the key "bad_key" to "good_key"
</li>
</ol>
<div class="col">
<h4>BEFORE</h4>
<pre id="jsInput"></pre>
</div>
<div class="col">
<h4>AFTER</h4>
<pre id="jsOutput"></pre>
</div>
</div>