[英]Find all ancestry of certain elements in a flat array of objects
我正在尝试实现一些特定的用例,但走到了死胡同。 我需要,给定一个像这样的平面对象数组(从另一个类似的帖子中复制它,因为我发现了几个类似的帖子,但没有一个符合我的用例,或者至少我还不够聪明,无法意识到如何调整可能的解决方案适合我的用例):
const myArr = [
{
id: '1',
parentId: '0',
},
{
id: '2',
parentId: '1',
},
{
id: '3',
parentId: '2',
},
{
id: '4',
parentId: '2',
},
{
id: '5',
parentId: '2',
},
{
id: '6',
parentId: '2',
},
{
id: '7',
parentId: '6',
},
{
id: '8',
parentId: '7',
}
]
然后我有另一个 ID 数组,如下所示:
const idArr = [2, 4, 8]
所以我需要从第一个数组中过滤,匹配ID的元素,即ID为2的元素,然后是ID 4和ID 8
然后,对于过滤后的数组中存在的每个元素,我需要找到它的祖先,直到到达根级别,然后构建一棵树
这里的问题是我已经实现了,但是在现实生活中这个数组会很大,里面有数千个元素,而且代码很可能会运行很多次
所以我正在寻找技术上性能最高的解决方案:我想说递归地构建一棵树几乎是以一种高性能的方式完成的,但不知何故,我在第 2 步中陷入了死胡同,获得了某些元素的所有祖先。
任何人都可以在这里带来一些光明吗? 非常感谢提前
const myArr = [ { id: '1', parentId: '0', }, { id: '2', parentId: '1', }, { id: '3', parentId: '2', }, { id: '4', parentId: '2', }, { id: '5', parentId: '2', }, { id: '6', parentId: '2', }, { id: '7', parentId: '6', }, { id: '8', parentId: '7', } ] const idArr = [2, 4, 8] elObj = {} for (const el of myArr) { elObj[el.id] = {"parentId": el.parentId} } const getAncestory = (id, res) => { if (elObj[id].parentId === '0') { res.push('0') return res } res.push(elObj[id].parentId) return getAncestory(elObj[id].parentId, res) } const res = [] idArr.forEach(el => { res.push(getAncestory(el.toString(), [el.toString()])) }) console.log(elObj) console.log(res)
以下是上述代码在时间复杂度方面的工作方式和执行方式:
i
的元素位于初始数组的索引i
处,我们可以跳过这一步。O(m)
时间,其中m
是初始元素到树的根元素的距离。 请注意,我们假设所有元素最终都连接到根元素(我们的基本情况parentId === '0'
)。 如果这个假设不正确,我们需要为此修改代码。n
元素需要为( idArr
的长度)构建祖先列表,整个过程需要O(n * m)
,因为操作都是不变的。1 + 2 +... n-1 + n
元素,其中离根最近的元素需要 1 步,最远的元素需要 n 步。 这导致n * (n+1)/2
步,在大 O 术语中是O(n^2)
。注意:如果您有数千个对象并且正在寻找数百个对象的祖先,则上述方法很好(我对数据做了很多假设)。 为了做出有根据的猜测,需要有关数据和要求的更多详细信息。
目前尚不清楚您要生成什么。 这个答案猜测你想要一棵树,它只包含指定了 id 的节点,以及它们的祖先。 它返回一个这样的结构:
[
{id: "1", children: [
{id: "2", children: [
{id: "4", children: []},
{id: "6", children: [
{id: "7", children: [
{id: "8", children: []}
]}
]}
]}
]}
]
请注意,这不是一棵树,而是一片森林。 我看不到任何证据表明每个谱系都以单个根节点结束,因此 output 可能有多个根。 但是,如果您的数据确实强制执行单根,您可以只取第一个元素。
此代码将执行此操作:
const unfold = (fn, init, res = []) => fn (init, (x, s) => unfold (fn, s, res.concat (x)), () => res) const filterByIds = (xs, map = Object.fromEntries(xs.map (({id, parentId}) => [id, parentId]))) => ( ids, selected = [...new Set (ids.map (String).flatMap ( id => unfold ((i, next, stop) => i in map? next (i, map [i]): stop(), id) ))] ) => xs.filter (({id}) => selected.includes (id)) const makeForest = (xs, root = 0) => xs.filter (({parentId}) => parentId == root).map (({id, parentId, ...rest}) => ({ id, ...rest, children: makeForest (xs, id) })) // [0] /* if the data really forms a tree */ const extractForest = (xs, ids) => makeForest (filterByIds (xs) (ids)) const myArr = [{id: "1", parentId: "0"}, {id: "2", parentId: "1"}, {id: "3", parentId: "2"}, {id: "4", parentId: "2"}, {id: "5", parentId: "2"}, {id: "6", parentId: "2"}, {id: "7", parentId: "6"}, {id: "8", parentId: "7"}] console.log ( extractForest (myArr, [2, 4, 8]) )
.as-console-wrapper {max-height: 100%;important: top: 0}
我们从有点有趣的unfold
助手 function 开始。 这使您可以从种子值开始,并通过重复调用您提供的 function 和两个 function 来将其转换为值数组,一个用于传递新值和下一个种子,另一个用于停止处理并返回到目前为止返回的值列表。 我们使用它来跟踪每个 id 的血统。 这绝不是我们可以这样做的唯一方法。 while
循环可能更熟悉,但它是一个有用的工具,它不涉及任何重新分配或可变变量,我真的很感激。
( unfold
如何工作的一个例子可能是一个简单的斐波那契数生成器:
const fibsTo = (n) =>
unfold (([a, b], next, stop) => b <= n ? next (b, [b, a + b]) : stop (), [0, 1])
fibsTo (100) //=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
这将调用next
一个斐波那契数和一个新种子,包括当前值和下一个种子,从种子[0, 1]
开始,当总数超过我们的目标数时,我们改为调用stop
。)
接下来,我们有 function filterByIds
,它接受您的输入数组,并返回一个 function ,它接受一个 id 列表并过滤该数组以仅包含这些 id 的祖先中的元素。 我们分三步进行。 首先,我们创建一个 Object ( map
) 将输入值的 id 映射到这些实际值。 其次,我们使用 function 对 id 进行平面映射,以检索其祖先列表; 这使用了我们上面的unfold
,但可以用while
循环重写。 我们使用[... new Set (/* above */)]
从这个列表中收集唯一值。 第三,我们过滤原始列表以仅包含其 id 在此新列表中的元素( selected
。)
function makeForest
——就像unfold
是相当通用的,采用{id, parentId, ...more}
节点的平面列表并将它们递归地嵌套在{id, ...more, children: []}
结构中。 如果您的数据是单根的,请取消注释最后一行中的[0]
。
最后我们有我们的主要 function extractForest
调用makeForest
pm filterByIds
的结果。
我想强调一下, unfold
和makeForest
都是非常通用的。 所以这里的自定义代码主要是filterByIds
和简单的extractForest
包装器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.