[英]Find all ancestry of certain elements in a flat array of objects
I'm trying to achieve some specific use case but come to a dead end.我正在尝试实现一些特定的用例,但走到了死胡同。 I need to, given an flat array of objects like this (Copying it from another similar post, as I found several similar posts but none matching my use case, or at least I haven't been smart enough to realise how to tweak possible solutions to fit my use-case):我需要,给定一个像这样的平面对象数组(从另一个类似的帖子中复制它,因为我发现了几个类似的帖子,但没有一个符合我的用例,或者至少我还不够聪明,无法意识到如何调整可能的解决方案适合我的用例):
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',
}
]
And then I have another array of IDs like so:然后我有另一个 ID 数组,如下所示:
const idArr = [2, 4, 8]
So I need to filter from the first array, elements with matching ID, ie the element with ID 2, then ID 4 and ID 8所以我需要从第一个数组中过滤,匹配ID的元素,即ID为2的元素,然后是ID 4和ID 8
And then, for each element present in the filtered array, I need to find it's ancestry until reach the root level, then build a tree然后,对于过滤后的数组中存在的每个元素,我需要找到它的祖先,直到到达根级别,然后构建一棵树
The problem here is that I already achieved it, but in real life this array will be huge, with thousands of elements in it, and the code will run most likely a lot of times这里的问题是我已经实现了,但是在现实生活中这个数组会很大,里面有数千个元素,而且代码很可能会运行很多次
So I am looking for the technically most performant possible solution: I'd say building a tree recursively is pretty much done in a performant way, but somehow I am in a dead end with step 2, getting all the ancestry of certain elements.所以我正在寻找技术上性能最高的解决方案:我想说递归地构建一棵树几乎是以一种高性能的方式完成的,但不知何故,我在第 2 步中陷入了死胡同,获得了某些元素的所有祖先。
Could anyone bring some light here?任何人都可以在这里带来一些光明吗? Thanks a lot in advance非常感谢提前
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)
Here's how the above code works and performs in terms of time complexity:以下是上述代码在时间复杂度方面的工作方式和执行方式:
i
is at index i
of the initial array.如果可以保证 id 为i
的元素位于初始数组的索引i
处,我们可以跳过这一步。O(m)
time where m
is the distance of the initial element to the root element of the tree.创建每个祖先列表需要O(m)
时间,其中m
是初始元素到树的根元素的距离。 Note that we have assumed that all elements are eventually connected to the root element (our base case of parentId === '0'
).请注意,我们假设所有元素最终都连接到根元素(我们的基本情况parentId === '0'
)。 If this assumption is not correct, we need to amend the code for that.如果这个假设不正确,我们需要为此修改代码。n
elements that you need to build the ancestry lists for (length of idArr
), this whole process takes O(n * m)
, since the operations are all constant.假设有n
元素需要为( idArr
的长度)构建祖先列表,整个过程需要O(n * m)
,因为操作都是不变的。1 + 2 +... n-1 + n
elements where the closest element to the root takes 1 step and the farthest away takes n steps.那是因为我们需要列出1 + 2 +... n-1 + n
元素,其中离根最近的元素需要 1 步,最远的元素需要 n 步。 This leads to n * (n+1)/2
steps which is O(n^2)
in Big O terms.这导致n * (n+1)/2
步,在大 O 术语中是O(n^2)
。Note: If you have a few thousands of objects and are looking for the ancestry of a few hundreds of them, the above approach is fine (I'm making a lot of assumptions about the data).注意:如果您有数千个对象并且正在寻找数百个对象的祖先,则上述方法很好(我对数据做了很多假设)。 To make an educated guess one needs more details about the data and requirements.为了做出有根据的猜测,需要有关数据和要求的更多详细信息。
It's not entirely clear what you're trying to generate.目前尚不清楚您要生成什么。 This answer makes the guess that you want a tree that includes only the nodes whose ids are specified, as well as their ancestors.这个答案猜测你想要一棵树,它只包含指定了 id 的节点,以及它们的祖先。 It returns a structure like this:它返回一个这样的结构:
[
{id: "1", children: [
{id: "2", children: [
{id: "4", children: []},
{id: "6", children: [
{id: "7", children: [
{id: "8", children: []}
]}
]}
]}
]}
]
Note that this is not a tree but a forest.请注意,这不是一棵树,而是一片森林。 I see nothing to demonstrate that every lineage ends up at a single root node, so the output might have multiple roots.我看不到任何证据表明每个谱系都以单个根节点结束,因此 output 可能有多个根。 But if your data does enforce the single root, you can just take the first element.但是,如果您的数据确实强制执行单根,您可以只取第一个元素。
This code will do that:此代码将执行此操作:
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}
We start with the somewhat interesting unfold
helper function.我们从有点有趣的unfold
助手 function 开始。 This lets you start with a seed value and turn it into an array of values by repeatedly calling the function you supply with the current seed and two function, one to pass along a new value and the next seed, the other to stop processing and return the list of values returned so far.这使您可以从种子值开始,并通过重复调用您提供的 function 和两个 function 来将其转换为值数组,一个用于传递新值和下一个种子,另一个用于停止处理并返回到目前为止返回的值列表。 We use this to track the lineage of each id.我们使用它来跟踪每个 id 的血统。 This is by no means the only way we could have done so.这绝不是我们可以这样做的唯一方法。 A while
loop is probably more familiar, but it's a useful tool, and it doesn't involve any reassignment or mutable variables, which I really appreciate. while
循环可能更熟悉,但它是一个有用的工具,它不涉及任何重新分配或可变变量,我真的很感激。
(An example of how unfold
works might be a simple Fibonacci number generator: ( 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]
This calls next
with the next Fibonnaci number and a new seed including the current value and the one which would come next, starting with the seed [0, 1]
When the total passes our target number, we instead call stop
.)这将调用next
一个斐波那契数和一个新种子,包括当前值和下一个种子,从种子[0, 1]
开始,当总数超过我们的目标数时,我们改为调用stop
。)
Next we have the function filterByIds
that takes your input array, and returns a function that accepts a list of ids and filters the array to include just those elements which are in the ancestry of one of those ids.接下来,我们有 function filterByIds
,它接受您的输入数组,并返回一个 function ,它接受一个 id 列表并过滤该数组以仅包含这些 id 的祖先中的元素。 We do this in three steps.我们分三步进行。 First, we create an Object ( map
) mapping the ids of our input values to those actual values.首先,我们创建一个 Object ( map
) 将输入值的 id 映射到这些实际值。 Second, we flatmap the ids with a function to retrieve the list of their ancestors;其次,我们使用 function 对 id 进行平面映射,以检索其祖先列表; this uses our unfold
above, but could be rewritten with a while
loop.这使用了我们上面的unfold
,但可以用while
循环重写。 And we use [... new Set (/* above */)]
to collect the unique values from this list.我们使用[... new Set (/* above */)]
从这个列表中收集唯一值。 Third, we filter the original list to include only the elements whose ids are in this new list ( selected
.)第三,我们过滤原始列表以仅包含其 id 在此新列表中的元素( selected
。)
The function makeForest
-- like unfold
is fairly generic, taking a flat list of {id, parentId, ...more}
nodes and nesting them recursively in an {id, ...more, children: []}
structure. function makeForest
——就像unfold
是相当通用的,采用{id, parentId, ...more}
节点的平面列表并将它们递归地嵌套在{id, ...more, children: []}
结构中。 Uncomment the [0]
in the last line if your data is singly rooted.如果您的数据是单根的,请取消注释最后一行中的[0]
。
And finally we have our main function extractForest
which calls makeForest
pm the result of filterByIds
.最后我们有我们的主要 function extractForest
调用makeForest
pm filterByIds
的结果。
I would like to stress that unfold
and makeForest
are both quite generic.我想强调一下, unfold
和makeForest
都是非常通用的。 So the custom code here is mostly filterByIds
, and the simple extractForest
wrapper.所以这里的自定义代码主要是filterByIds
和简单的extractForest
包装器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.