简体   繁体   English

在深层嵌套数组(树)中查找递归

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM