如何在返回单个对象时递归使用Array.prototype.find()?

时间:2016-04-28 12:37:13

标签: javascript recursion

我想解决的更大问题是,根据这些数据:

var data = [
    { id: 1 },
    { id: 2 },
    { id: 3 },
    { id: 4, children: [
        { id: 6 },
        { id: 7, children: [
            {id: 8 },
            {id: 9 }
        ]}
    ]},
    { id: 5 }
]

我想创建一个返回findById(data, id)的函数{ id: id }。例如,findById(data, 8)应返回{ id: 8 }findById(data, 4)应返回{ id: 4, children: [...] }

为了实现这一点,我递归地使用了Array.prototype.find,但是当return将对象混合在一起时遇到了麻烦。我的实现将路径返回给特定对象。

例如,当我使用findById(data, 8)时,它会将路径返回到{ id: 8 }

 { id: 4, children: [ { id: 6 }, { id: 7, children: [ { id: 8}, { id: 9] } ] }

相反,我希望它只是返回

{ id: 8 }

实施(Node.js v4.0.0)

jsfiddle

var data = [
    { id: 1 },
    { id: 2 },
    { id: 3 },
    { id: 4, children: [
        { id: 6 },
        { id: 7, children: [
            {id: 8 },
            {id: 9 }
        ]}
    ]},
    { id: 5 }
]

function findById(arr, id) {
    return arr.find(a => {
        if (a.children && a.children.length > 0) {
            return a.id === id ? true : findById(a.children, id)
        } else {
            return a.id === id
        }
    })
    return a
}

console.log(findById(data, 8)) // Should return { id: 8 }

// Instead it returns the "path" block: (to reach 8, you go 4->7->8)
//
// { id: 4,
//   children: [ { id: 6 }, { id: 7, children: [ {id: 8}, {id: 9] } ] }

11 个答案:

答案 0 :(得分:14)

你所拥有的问题是发现的冒泡。如果在嵌套结构中找到id,则回调会尝试返回元素,该元素被解释为true,即find的值。

  

find方法对数组中存在的每个元素执行一次回调函数,直到找到一个回调返回true值的元素。 [MDN]

如果找到,我会建议使用递归样式进行短路搜索。



var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];

function findById(data, id) {
    function iter(a) {
        if (a.id === id) {
            result = a;
            return true;
        }
        return Array.isArray(a.children) && a.children.some(iter);
    }

    var result;
    data.some(iter);
    return result
}

console.log(findById(data, 8));




答案 1 :(得分:3)

我只会使用常规循环和递归样式搜索:

function findById(data, id) {
    for(var i = 0; i < data.length; i++) {
        if (data[i].id === id) {
            return data[i];
        } else if (data[i].children && data[i].children.length && typeof data[i].children === "object") {
            findById(data[i].children, id);
        }
    }
}

//findById(data, 4) => Object {id: 4, children: Array[2]}
//findById(data, 8) => Object {id: 8}

答案 2 :(得分:1)

让我们考虑基于递归调用的实现:

Dim Mypath As String, fileName As String, mps As Variant, mps_temp As String, mydate As Date, IntroDate As Date, i as integer

Application.ScreenUpdating = False

Mypath = "C:\Users\Kirank\Documents\Stock Feed Analysis\"

fileName = Dir(Mypath & "*.xl?")    'Dir functions support use of wildcards character * and ? used to look for all types of excel files in the folder.


Do While fileName <> ""

Count = Count + 1

mps = Split(fileName, " ")

For i = LBound(mps) To UBound(mps)
  mps_temp = mps(UBound(mps) - i)
    If mps_temp Like "####.##.##.xlsx" Then
      mydate = DateSerial(Mid(mps_temp, 1, 4), Mid(mps_temp, 6, 2),Mid(mps_temp, 9,2))
        IntroDate = mydate - 181      

        Cells(Count, 1).Value = IntroDate    'Saving Introdate in excel, feel free to change the destination.
        Exit For
    End If
Next i

fileName = Dir()

Loop

用法

function findById(tree, nodeId) {
  for (let node of tree) {
    if (node.id === nodeId) return node

    if (node.children) {
      let desiredNode = findById(node.children, nodeId)
      if (desiredNode) return desiredNode
    }
  }
  return false
}

为简化图片?,请想象一下:

  • 你是坐在棕榈树palm上的猴子;
  • 然后寻找成熟的香蕉,顺着树
  • 您最终还是对搜索不满意;
  • 回到树的顶部,然后从下一个分支重新开始;
  • 如果您尝试了树上所有的香蕉,但没人满意,您只是断言,成熟的香蕉不会在这棵棕榈树上生长;
  • 但是,如果发现了香蕉,您就会回到顶端,并乐于食用它。

现在让我们尝试将其应用于递归算法algorithm:

  1. 从顶部节点(从树的顶部开始)开始迭代;
  2. 如果在迭代中找到了该节点,则返回该节点(如果香蕉已经成熟);
  3. 深入研究,直到找到项目为止,否则将没有任何内容可追溯。将搜索结果保留到变量(将搜索结果保留为香蕉还是什么都不包含,然后返回顶部);
  4. 如果搜索结果变量包含所需的节点,则返回搜索结果变量。(如果发现则吃掉香蕉,否则请记住不要回到该分支下);
  5. 如果未找到节点,请保持迭代(如果未找到香蕉,请继续测试其他分支);
  6. 如果在所有迭代之后都没有找到所需的节点,则返回false。(断言该树上没有成熟的香蕉)

第一次进行学习递归似乎并不容易?,但是这种技术可以让您以优雅的方式解决日常问题。

答案 3 :(得分:1)

我知道这是一个古老的问题,但是由于最近又有了一个答案,我将把另一个版本混为一谈。

我将树遍历和测试与要测试的实际谓词分开。我相信这可以使代码更简洁。

基于reduce的解决方案如下所示:

const nestedFind = (pred) => (xs) =>
  xs .reduce (
    (res, x) => res ? res : pred(x) ? x : nestedFind (pred) (x.children || []), 
    undefined
  )
 
const findById = (testId) => 
  nestedFind (({id}) => id == testId)


const data = [{id: 1}, {id: 2}, {id: 3}, {id: 4, children: [{id: 6}, {id: 7, children: [{id: 8}, {id: 9}]}]}, {id: 5}]

console .log (findById (8) (data))
console .log (findById (4) (data))
console .log (findById (42) (data))
.as-console-wrapper {min-height: 100% !important; top: 0}

有几种方法可以用主列表上的迭代替换reduce。这样的事情会做同样的事情:

const nestedFind = (pred) => ([x = undefined, ...xs]) =>
  x == undefined
    ? undefined
  : pred (x)
    ? x
  : nestedFind (pred) (x.children || []) || nestedFind (pred) (xs)

我们可以轻松完成尾递归操作。

尽管我们可以将这两个函数合而为一,并获得更短的代码,但我认为nestedFind提供的灵活性将使其他类似问题更加容易。但是,如果您感兴趣,第一个可能看起来像这样:

const findById = (id) => (xs) =>
  xs .reduce (
    (res, x) => res ? res : x.id === id ? x : findById (id) (x.children || []), 
    undefined
  )

答案 4 :(得分:0)

基于Purkhalo Alex解决方案,

我对他的功能进行了修改,使其能够基于给定的动态属性来递归地找到ID,然后返回您要查找的值还是之后递归地到达对象或属性的索引数组。 / p>

这就像findfindIndex一样,通过对象数组以及给定属性中的嵌套对象数组实现。

findByIdRecursive(tree, nodeId, prop = '', byIndex = false, arr = []) {
    for (let [index, node] of tree.entries()) {
        if (node.id === nodeId) return byIndex ? [...arr, index] : node;

        if (prop.length && node[prop].length) {
            let found = this.findByIdRecursive(node[prop], nodeId, prop, byIndex, [
                ...arr,
                index
            ]);
            if (found) return found;
        }
    }
    return false;
}

现在您可以控制搜索的属性和类型并获得正确的结果。

答案 5 :(得分:0)

这可以通过reduce来解决。

const foundItem = data.reduce(findById(8), null)
function findById (id) {
  const searchFunc = (found, item) => {
    const children = item.children || []
    return found || (item.id === id ? item : children.reduce(searchFunc, null))
  }
  return searchFunc
}

答案 6 :(得分:0)

可以Array.prototype.find()Array.prototype.flatMap()结合使用

const findById = (a, id, p = "children", u) => 
  a.length ? a.find(o => o.id === id) || findById(a.flatMap(o => o[p] || []), id) : u; 


const tree = [{id:1}, {id:2}, {id:3}, {id:4, children:[{id: 6}, {id:7, children:[{id:8}, {id:9}]}]}, {id:5}];
console.log(findById(tree, 9));  // {id:9}
console.log(findById(tree, 10)); // undefined

答案 7 :(得分:0)

如果有人想使用 Array.prototype.find,这是我选择的选项:

    findById( my_big_array, id ) {
        var result;

        function recursiveFind( haystack_array, needle_id ) {
            return haystack_array.find( element => {
                if ( !Array.isArray( element ) ) {
                    if( element.id === needle_id ) {
                        result = element;
                        return true;
                    }
                } else {
                    return recursiveFind( element, needle_id );
                }
            } );
        }
        recursiveFind( my_big_array, id );

        return result;
    }

您需要 result 变量,因为没有它,该函数将返回包含结果的数组中的顶级元素,而不是对包含匹配 id 的深层嵌套对象的引用,这意味着您需要过滤更进一步。

查看其他答案后,我的方法似乎与 Nina Scholz's 非常相似,但使用的是 find() 而不是 some()

答案 8 :(得分:0)

在我看来,如果你想通过 id 递归搜索,最好使用这样的算法:

function findById(data, id, prop = 'children', defaultValue = null) {
  for (const item of data) {
    if (item.id === id) {
      return item;
    }

    if (Array.isArray(item[prop]) && item[prop].length) {
      const element = this.findById(item[prop], id, prop, defaultValue);

      if (element) {
        return element;
      }
    }
  }

  return defaultValue;
}

findById(data, 2);

但我强烈建议使用更灵活的函数,它可以通过任何键值对/对进行搜索:

function findRecursive(data, keyvalues, prop = 'children', defaultValue = null, _keys = null) {
  const keys = _keys || Object.keys(keyvalues);

  for (const item of data) {
    if (keys.every(key => item[key] === keyvalues[key])) {
      return item;
    }

    if (Array.isArray(item[prop]) && item[prop].length) {
      const element = this.findRecursive(item[prop], keyvalues, prop, defaultValue, keys);

      if (element) {
        return element;
      }
    }
  }

  return defaultValue;
}

findRecursive(data, {id: 2});

答案 9 :(得分:0)

这是一个不是最短的解决方案,而是将问题分解为递归迭代和在可迭代对象(不一定是数组)中查找项目。

您可以定义两个通用函数:

  • deepIterator:以预序方式遍历森林的生成器
  • iFind:一个查找器,如 Array#find,但适用于可迭代对象

function * deepIterator(iterable, children="children") {
    if (!iterable?.[Symbol.iterator]) return;
    for (let item of iterable) {
        yield item;
        yield * deepIterator(item?.[children], children);
    }
}

function iFind(iterator, callback, thisArg) {
    for (let item of iterator) if (callback.call(thisArg, item)) return item;
}
  
// Demo
var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];

console.log(iFind(deepIterator(data), ({id}) => id === 8));

答案 10 :(得分:-1)

Roko C. Buljan 的解决方案,但更具可读性:

function findById(data, id, prop = 'children', defaultValue = null) {
  if (!data.length) {
    return defaultValue;
  }

  return (
    data.find(el => el.id === id) ||
    findById(
      data.flatMap(el => el[prop] || []),
      id
    )
  );
}