简体   繁体   English

在平面对象数组中查找某些元素的所有祖先

[英]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:以下是上述代码在时间复杂度方面的工作方式和执行方式:

  1. Creating an object of objects where elements can be accessed in constant time based on their ids is a linear operation, both time and space-wise.创建一个 object 对象,其中元素可以根据它们的 id 在恒定时间内访问,这是一种线性操作,无论是时间还是空间。 We could have skipped this step if there was a guarantee that say element with id i is at index i of the initial array.如果可以保证 id 为i的元素位于初始数组的索引i处,我们可以跳过这一步。
  2. Creating each ancestry list takes 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.如果这个假设不正确,我们需要为此修改代码。
  3. Assuming that there are 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) ,因为操作都是不变的。
  4. This algorithm can deteriorate into a quadratic one in terms of the number of nodes in the tree in case the tree has the shape of a flat linked list and you want the ancestry of all of its elements.如果树具有扁平链表的形状并且您想要其所有元素的祖先,则该算法可能会根据树中的节点数恶化为二次算法。 That's because we would need to list 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)
  5. One way to amend it is to change the representation of the tree with parent to child pointers.修改它的一种方法是使用父子指针更改树的表示。 Then we can start from the root and backtrack, traversing all the possible paths and saving those of interest.然后我们可以从根和回溯开始,遍历所有可能的路径并保存感兴趣的路径。 This approach could be beneficial or worse the proposed one depending on data and the exact requirements for the output.根据数据和 output 的确切要求,这种方法可能是有益的,也可能更糟。

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.我想强调一下, unfoldmakeForest都是非常通用的。 So the custom code here is mostly filterByIds , and the simple extractForest wrapper.所以这里的自定义代码主要是filterByIds和简单的extractForest包装器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM