【问题标题】:Finding the parents of specific elements in a flat array/tree recursively递归查找平面数组/树中特定元素的父级
【发布时间】:2021-01-15 11:16:05
【问题描述】:

我有一个包含多个对象(孩子和父母)的平面数组。每个对象都包含一个 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。

【问题讨论】:

  • 你能改变对象吗?只需通过存储父引用来记忆函数。
  • 你的意思是只存储 parentIds 而不是整个父对象?
  • 也许我误解了这个问题。我以为您想优化递归调用,这可能通过记忆化(实际上,与您目前拥有的算法不同)是可能的。但是,如果您当前正在寻找的只是为了防止结果中出现重复,那么更简单的解决方案是只将 entityArray 设为 new Set 而不是数组。

标签: javascript recursion


【解决方案1】:

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

// ...
// 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;
    }, []),
);

【讨论】:

  • 谢谢,工作得很好,但你能解释一下为什么会这样吗?在我看来,这是相同的条件检查相同的数组,只是在不同的地方。
  • @RawDough 这不是很有用,但老实说我不确定它为什么会起作用。我的直觉是在推入阵列之前放置阵列检查。根据我的经验,像这样的嵌套 for 循环很快就会变得非常混乱。我在我的答案中添加了另一种方法,这可能会有所帮助——抱歉重新做你的工作。这可能只是我习惯的做法,但功能更强大的结构有助于我更轻松地遵循逻辑。
猜你喜欢
  • 2018-02-23
  • 1970-01-01
  • 1970-01-01
  • 2015-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多