[英]find in deep nested array (tree) recursivley
I want to find a value in the following nested array without using loops :我想在不使用循环的情况下在以下嵌套数组中找到一个值:
let children = [
{
name: 'grand 1',
children: [
{
name: 'parent 1.1',
children: [
{
name: 'child 1.1.1',
children: [
// more...
]
},
// more...
],
},
// more...
],
},
// more...
];
This is what I'd do if I was only searching in the horizontal axis:如果我只在水平轴上搜索,这就是我要做的:
let childrenHorizontal = [
{ name: 'grand 1' },
{ name: 'grand 2' },
{ name: 'grand 3' },
// more
];
function findHorizontal(name, childrenHorizontal) {
let [head, ...tail] = childrenHorizontal;
if (head.name === name)
return head;
else if (tail.length)
return findHorizontal(name, tail);
}
But how do I search the nested array both horizontally and vertically?但是如何水平和垂直搜索嵌套数组?
A bit more generically, we can write a deepFind
function that takes an arbitrary predicate and returns a function that tests a tree in a depth-first manner until it finds a matching node, returning undefined
if no match is found.更一般地说,我们可以编写一个
deepFind
function,它采用任意谓词并返回一个 function,它以深度优先的方式测试树,直到找到匹配的节点,如果没有找到匹配则返回undefined
。 (This is JS, after all!) Then we can write findByName
as a function that that takes a target name and passes to deepFind
a function that tests whether a given node's name
matches that target. (毕竟这是 JS!)然后我们可以将
findByName
写成一个 function ,它接受一个目标名称并传递给deepFind
一个 function 来测试给定节点的name
是否与该目标匹配。 It might look like this:它可能看起来像这样:
const deepFind = (pred) => ([head, ...tail]) => head == undefined? undefined: pred (head)? head: deepFind (pred) (head.children) || deepFind (pred) (tail) const findByName = (target) => deepFind (({name}) => name == target) let children = [{name: 'grand 1', children: [{name: 'parent 1.1', children: [{name: 'child 1.1.1', children: []}]}, {name: 'parent 1.2', children: []}]}] console.log ('parent 1.1:', findByName ("parent 1.1") (children)) console.log ('child 1.1.1:', findByName ("child 1.1.1") (children)) console.log ('parent 1.2:', findByName ("parent 1.2") (children))
.as-console-wrapper {max-height: 100%;important: top: 0}
(Note that I added a parent 1.2
node in the obvious location in the tree to demonstrate searching multiple children of one node.) (请注意,我在树中的明显位置添加了一个
parent 1.2
节点,以演示搜索一个节点的多个子节点。)
This finds the first node in a pre-order traversal of the tree that matches our predicate.这会找到与我们的谓词匹配的树的预序遍历中的第一个节点。 We use the short-circuiting feature of JavaScript's
||
我们使用 JavaScript 的
||
的短路功能operator to stop as soon as we've found a match.运算符在我们找到匹配项后立即停止。
Ok I figured it out.好的,我想通了。 The trick is to concatenate the two axes:
诀窍是连接两个轴:
function find(name, children) {
let [head, ...tail] = children;
if (head.name === name)
return head;
return find(name, [...head.children, ...tail]);
}
But then I realized it is wasteful to concatenate the arrays when I can just use logical OR (using the null-coalescing operator ):但后来我意识到,当我只能使用逻辑或(使用空合并运算符)时,连接 arrays 是一种浪费:
function find(name, children) {
let [head, ...tail] = children
if (head.name === name) return head
return find(name, head.children) ?? find(name, tail) // no concat!
}
And I also learned that you must always check if the input array is empty, otherwise head
could be undefined!而且我还了解到,您必须始终检查输入数组是否为空,否则
head
可能未定义!
function find(name, children) {
if (children.length == 0) return; // exit early if children is empty
let [head, ...tail] = children;
if (head.name === name) return head;
return find(name, head.children) ?? find(name, tail);
}
One possible way to restore the tail call is to use continuation-passing style:恢复尾调用的一种可能方法是使用连续传递样式:
const identity = x => x
function find(name, children, k = identity) {
if (children.length == 0) return;
let [head, ...tail] = children;
if (head.name === name) return k(head);
return find(name, head.children, result => // tail find
result ?? find(name, tail, k) // tail find
);
}
I also learned that destructuring assignment let [head, ...tail]
creates new tail
arrays - for every node along the way!我还了解到解构赋值
let [head, ...tail]
创建新的tail
arrays - 沿途的每个节点! find
is a sort of read operation on the structure, so it would be wasteful to create single-use data along the way: find
是对结构的一种读取操作,因此在此过程中创建一次性数据会很浪费:
function find(name, children, i = 0) {
if (i >= children.length) return; // exit if i is out of bounds
let head = children[i] // "head" is now a cursor over the array
if (head.name === name) return head;
return find(name, head.children) ?? find(name, children, i + 1);
}
If I want the user to prevent accidentally passing initial i
argument, I can wrap in an outer function. And I could add the CPS technique for tail call, if needed.如果我希望用户防止意外传递初始
i
参数,我可以在外部包装 function。如果需要,我可以为尾调用添加 CPS 技术。
You need to use recursion:您需要使用递归:
const data = [{ name: 'grand 1', children: [{ name: 'parent 1.1', children: [{ name: 'child 1.1.1', children: [] }, // more... ], }, // more... ], }, // more... ]; const find = (name, children) => { for (const child of children) { // if the chidren's name corresopnds to the one provided, return it if (child.name === name) return child // here is the recursion const e = find(name, child.children) // if we found it on the child, we return the one found, else we keep going if (e) return e } // if none of the children or grand-children... found, return null return null } console.log(find('parent 1.1', data))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.