根据值将对象数组转换为嵌套对象

Convert array of objects to nested object based on values

如何根据开始和结束之间的值将对象数组转换为嵌套对象?

假设我有一个这样的数组:

const array = [
  {
      "content": "_88888888888  ~*8888888888*~  *8888888*_",
      "start": 5,
      "end": 37
  },
  {
      "content": "~*88888*~",
      "start": 18,
      "end": 27
  },
  {
      "content": "*88888*",
      "start": 19,
      "end": 26
  },
  {
      "content": "*88888*",
      "start": 29,
      "end": 36
  }
]

我想将其转换为:

const array = [
  {
      "content": "_88888888888  ~*88888*~  *88888*_",
      "start": 5,
      "end": 37,
      "children": [
        {
          "content": "~*88888*~",
          "start": 18,
          "end": 27,
          "children": [
            {
              "content": "*8888888888*",
              "start": 19,
              "end": 26
            },
          ]
        },
        {
          "content": "*88888*",
          "start": 29,
          "end": 36
        }
      ]
  }
]

如您所见,在预期结果中,每个子值都有匹配起始值和结束值的父对象。

我将按 span-width 降序迭代数组 objects。创建一个具有无限跨度的虚拟根节点。然后将每个 object 插入该树中:

  • 首先找出当前树节点是否有一个child可以成为object的一个parent。如果是这样,使 child 成为当前的,然后重复。
  • 一旦没有这样的child,将object追加到当前节点的children集合中。

这是一个实现:

const array = [{"content": "_88888888888  ~*8888888888*~  *8888888*_","start": 5,"end": 37},{"content": "~*88888*~","start": 18,"end": 27},{"content": "*88888*","start": 19,"end": 26},{"content": "*88888*","start": 29,"end": 36}];

const root = {
    start: -Infinity,
    end: Infinity,
    children: []
};

// Iterate in order of descending span width
for (let obj of [...array].sort((a, b) => (b.end - b.start) - (a.end - a.start))) {
    let child = root, 
        children;
    // Find and drill down    
    do {
        children = (child.children ??= []);
        child = children.find(child => child.start <= obj.start && child.end >= obj.end);
    } while (child);
    // Insert
    children.push({...obj});
}

console.log(root.children);

与大多数 arbitrary-nesting 场景一样,我们可以通过一定程度的递归来做到这一点。

const excluding = (os) => (xs) =>
  xs .filter ((x) => ! os .includes (x))
  
const minBy = (fn) => (xs) =>
  xs .reduce (
    ({m, c}, x, _, __, v = fn (x)) => v < m ? {m: v, c: x} : {m, c}, 
    {m: Infinity}
  ) .c

const restructure = (
  xs, 
  o = minBy (x => x .start) (xs), 
  kids = xs .filter ((x) => x !== o && x .start >= o .start && x .end <= o .end)
) => xs .length ? [
  {...o, ...(kids .length ? {children: restructure (kids)} : {})},
  ... restructure (excluding ([o, ...kids]) (xs)) 
] : []

const array = [{content: "_88888888888  ~*8888888888*~  *8888888*_", start: 5, end: 37}, {content: "~*88888*~", start: 18, end: 27}, {content: "*88888*", start: 19, end: 26}, {content: "*88888*", start: 29, end: 36}]

console .log (restructure (array))
.as-console-wrapper {max-height: 100% !important; top: 0}

我们从辅助函数 excluding 开始,它过滤一个列表以包括所有不在另一个列表中的函数(类似于集差函数),并且minBy,它根据提供的函数的结果找到列表的最小元素。

我们的主函数通过起始值找到第一个元素,找到该元素的所有后代(基于比较起始值和结束值),在这些后代上重复创建一个 children 节点,并在剩余元素上重复查找输出的后续元素。

这与 Trincot 的回答略有不同。这里同一级别的元素按起始值递增排序。该答案通过减小范围大小对它们进行排序。