递归查找平面数组/树中特定元素的父元素

时间:2021-01-15 11:16:05

标签: javascript recursion

我有一个包含多个对象(子对象和父对象)的平面数组。每个对象都包含一个 id、一个 parentId 和一个作为“conditionId”的第三个值。

我想要做的是根据conditionId过滤数组,然后找到父母,父母的父母,然后那些父母等等,直到我到达“根级”。

我已经有一个递归函数可以做到这一点:

function findParents (directory, children) {
    // creating an empty array to push the matching entities into
    const entityArray = []

    // looping through every entity in the directory
    for (const entity of directory) {

        // looping through every child of the children-array
        for (const child of children) {

            // checking if the parentId is not 0 and if the id of the entity matches the parentId of the child
            if (child.parentId !== 0 && entity.id === child.parentId) {

                // pushing the found entity to entityArray
                entityArray.push(entity)
            }
        }
    }

    let recursiveResult = []
    if (entityArray.length > 0) {
        // calling the function
        recursiveResult = findParents(directory, entityArray)
    }

    // returning the entityArray plus the result of the recursive call of this function
    const result = entityArray.concat(recursiveResult)

    return result
}

唯一的问题是,如果数组中的任何元素具有相同的父元素,则会多次找到该父元素,我想避免这种情况,因为这意味着通过简单地检查这些元素是否可以避免不必要的操作已经找到了。

应该发生什么的例子:

平面阵列:

const myArr = [
    {
        id: '1',
        parentId: '0',
        conditionId: 'apple'
    },
    {
        id: '2',
        parentId: '1',
        conditionId: 'apple'
    },
    {
        id: '3',
        parentId: '2',
        conditionId: 'banana'
    },
    {
        id: '4',
        parentId: '2',
        conditionId: 'banana'
    },
    {
        id: '5',
        parentId: '2',
        conditionId: 'apple'
    },
    {
        id: '6',
        parentId: '2',
        conditionId: 'apple'
    }
]

通过conditionId找到的孩子:

const foundByCondition = [
    {
        id: '3',
        parentId: '2',
        conditionId: 'banana'
    },
    {
        id: '4',
        parentId: '2',
        conditionId: 'banana'
    },
]

在第一个函数调用中,将传递 myArr 和 foundByCondition 作为参数。

结果应该是这样的:

result: [
    {
        id: '1',
        parentId: '0',
        conditionId: 'apple'
    },
    {
        id: '2',
        parentId: '1',
        conditionId: 'apple'
    }
]

但实际上看起来像这样:

result: [
    {
        id: '1',
        parentId: '0',
        conditionId: 'apple'
    },
    {
        id: '2',
        parentId: '1',
        conditionId: 'apple'
    },
    {
        id: '2',
        parentId: '1',
        conditionId: 'apple'
    }
]

我尝试添加一个 if 语句来检查该元素是否已经被找到一次:

    const entityArray: object[] = []

    // looping through every entity in the directory
    for (const entity of directory) {

        // checking if entityArray already contains entity of the loop
        if (!entityArray.some(item => item.id === entity.id)) {

            // looping through every child of the children-array
            for (const child of children) {

                // checking if the parentId is not 0 and if the id of the entity matches the parentId of the child
                if (child.parentId !== 0 && entity.id === child.parentId) {

                    // pushing the found entity to entityArray
                    entityArray.push(entity)
                }
            }
        }
    }
}

但它每次都返回true。

1 个答案:

答案 0 :(得分:0)

我认为您添加的条件在错误的地方。在函数中间试试这个:

// ...
// checking if the parentId is not 0 and if the id of the entity matches the parentId of the child
if (
    child.parentId !== 0 &&
    entity.id === child.parentId &&
    !entityArray.includes(entity)
) {
    entityArray.push(entity)
}
// ...

替代实现

这不是代码审查交流,而是另一种更FP 的方法。我发现像这样的链接数组方法确实有助于理解嵌套 for 循环的事物。这几乎肯定会更慢,但只有当您要查询庞大的数据集或非常频繁地运行时,这才是真正重要的。

const directory = [
  { id: '1', parentId: '0', conditionId: 'apple' },
  { id: '2', parentId: '1', conditionId: 'apple' },
  { id: '3', parentId: '2', conditionId: 'banana' },
  { id: '4', parentId: '2', conditionId: 'banana' },
  { id: '5', parentId: '2', conditionId: 'apple' },
  { id: '6', parentId: '2', conditionId: 'apple' },
];

/**
 * Return an array of all ancestors of the provided item in the provided
 * directory by following `parentId` properties up the hierarchy.
 *
 * The item with `parentId: '0'` is assumed to be the root node.
 */
function findAncestors(item, directory) {
  if (item.parentId === '0') return [];

  const parent = directory.find(i => i.id === item.parentId);

  return [
    parent,
    ...findAncestors(parent, directory),
  ];
}

console.log(
  directory
    // Filter the items down to the ones we care about
    .filter(item => item.conditionId === 'banana')
    // Map each item to an array of its ancestors
    .map(item => findAncestors(item, directory))
    // Flatten the array of arrays into an array of items
    .flat()
    // De-duplicate the result
    .reduce((output, item) => {
      return !output.includes(item)
        ? [...output, item]
        : output;
    }, []),
);