繁体   English   中英

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

[英]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)

以下是上述代码在时间复杂度方面的工作方式和执行方式:

  1. 创建一个 object 对象,其中元素可以根据它们的 id 在恒定时间内访问,这是一种线性操作,无论是时间还是空间。 如果可以保证 id 为i的元素位于初始数组的索引i处,我们可以跳过这一步。
  2. 创建每个祖先列表需要O(m)时间,其中m是初始元素到树的根元素的距离。 请注意,我们假设所有元素最终都连接到根元素(我们的基本情况parentId === '0' )。 如果这个假设不正确,我们需要为此修改代码。
  3. 假设有n元素需要为( idArr的长度)构建祖先列表,整个过程需要O(n * m) ,因为操作都是不变的。
  4. 如果树具有扁平链表的形状并且您想要其所有元素的祖先,则该算法可能会根据树中的节点数恶化为二次算法。 那是因为我们需要列出1 + 2 +... n-1 + n元素,其中离根最近的元素需要 1 步,最远的元素需要 n 步。 这导致n * (n+1)/2步,在大 O 术语中是O(n^2)
  5. 修改它的一种方法是使用父子指针更改树的表示。 然后我们可以从根和回溯开始,遍历所有可能的路径并保存感兴趣的路径。 根据数据和 output 的确切要求,这种方法可能是有益的,也可能更糟。

注意:如果您有数千个对象并且正在寻找数百个对象的祖先,则上述方法很好(我对数据做了很多假设)。 为了做出有根据的猜测,需要有关数据和要求的更多详细信息。

目前尚不清楚您要生成什么。 这个答案猜测你想要一棵树,它只包含指定了 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的结果。

我想强调一下, unfoldmakeForest都是非常通用的。 所以这里的自定义代码主要是filterByIds和简单的extractForest包装器。

暂无
暂无

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

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