【问题标题】:Group by Object from Array Object to Tree Object Javascript按对象从数组对象分组到树对象Javascript
【发布时间】:2021-01-30 03:25:02
【问题描述】:

我需要像这里这样的帮助组对象: 输入数组是:

[
    {
        Id: '1234',
        Name: 'Country - Viet Nam',
        AdminLevel: 2
    },
    {
        Id:'5678',
        Name: 'Province - Ho Chi Minh',
        AdminLevel: 4,
        ParentId: '1234'
    },
    {
        Id:'91011',
        Name: 'Province - Ha Noi',
        AdminLevel: 4,
        ParentId: '1234'
    },
    {
        Id:'111213',
        Name: 'Province - Da Nang',
        AdminLevel: 4,
        ParentId: '1234'
    },
    {
        Id:'111213',
        Name: 'District - Quan 1',
        AdminLevel: 6,
        ParentId: '5678'
    },
    {
        Id:'111213',
        Name: 'District - Quan 2',
        AdminLevel: 6,
        ParentId: '5678'
    },
    {
        Id:'111213',
        Name: 'District - Quan 3',
        AdminLevel: 6,
        ParentId: '5678'
    },
    {
        Id:'111213',
        Name: 'District - Tinh nao do Ha Noi 1',
        AdminLevel: 6,
        ParentId: '91011'
    },
    {
        Id:'111213',
        Name: 'District - Tinh nao do Ha Noi 2',
        AdminLevel: 6,
        ParentId: '91011'
    },
    {
        Id:'111213',
        Name: 'District - Tinh nao do Ha Noi 3',
        AdminLevel: 6,
        ParentId: '91011'
    }
    
]

期望输出是:

{
    Id: '1234',
    Name: 'Country - Viet Nam',
    AdminLevel: 2,
    Data: [
        {
            Id:'5678',
            Name: 'Province - Ho Chi Minh',
            AdminLevel: 4,
            ParentId: '1234',
            Data: [
                {
                    Id:'111213',
                    Name: 'District - Quan 1',
                    AdminLevel: 6,
                    ParentId: '5678'
                },
                {
                    Id:'111213',
                    Name: 'District - Quan 2',
                    AdminLevel: 6,
                    ParentId: '5678'
                },
                {
                    Id:'111213',
                    Name: 'District - Quan 3',
                    AdminLevel: 6,
                    ParentId: '5678'
                }
            ]
        },
        {
            Id:'91011',
            Name: 'Province - Ha Noi',
            AdminLevel: 4,
            ParentId: '1234',
            Data: [
                {
                    Id:'111213',
                    Name: 'District - Tinh nao do Ha Noi 1',
                    AdminLevel: 6,
                    ParentId: '91011'
                },
                {
                    Id:'111213',
                    Name: 'District - Tinh nao do Ha Noi 2',
                    AdminLevel: 6,
                    ParentId: '91011'
                },
                {
                    Id:'111213',
                    Name: 'District - Tinh nao do Ha Noi 3',
                    AdminLevel: 6,
                    ParentId: '91011'
                }
            ]
        },
        {
            Id:'111213',
            Name: 'Province - Da Nang',
            AdminLevel: 4,
            ParentId: '1234'
        },
    ]
}

说明:我们必须按 AdminLevel 分组,然后按 ParentId 分组。

  • 看一下,我们有结构:国家(父)- 省(多个- 国家的子)- 区(多个- 省的子)。 非常感谢你帮助我

【问题讨论】:

  • 所以相同的 parentId 将具有相同的AdminLevel。你的数据结构看起来是这样。
  • @Derek.W 这可能是巧合。我的猜测是它们是基于ParentIdId 匹配的。
  • 我有一个小问题,为什么Id 属性不是唯一的?通常,您希望 id标识 资源。在您的示例数据中,有 7 个对象具有相同的 Id (111213)。
  • 哎呀,Id 字段不是唯一标识符?其中一半拥有Id"111213",这确实搞砸了最直接的方法。
  • 这里的答案不关心AdminLevel,因为那是多余的信息; ParentIdAdminLevel 都对条目应该在树中的位置进行编码,但 ParentId 直接告诉你,而 AdminLevel 充其量只是一个检查。

标签: javascript typescript group-by reduce arrayobject


【解决方案1】:

假设您的数据是有序的,以便父母先于孩子,这相当简单:

  1. 遍历所有节点
  2. 为每个找到父项
  3. 添加到父级的Data 属性(如果还没有,则先创建它)
  4. 返回根节点

根节点将是没有任何父节点的节点。您可以使用地图来跟踪访问过的项目并更轻松地查找父母。

由于您的数据没有唯一的 ID,因此您需要为每个 ID 保留一组项目。如果你的数据碰巧有一个父 ID 匹配两个或更多的东西,那么它们每个都会有子节点。

const data = [ { Id: '1234', Name: 'Country - Viet Nam', AdminLevel: 2 }, { Id:'5678', Name: 'Province - Ho Chi Minh', AdminLevel: 4, ParentId: '1234' }, { Id:'91011', Name: 'Province - Ha Noi', AdminLevel: 4, ParentId: '1234' }, { Id:'111213', Name: 'Province - Da Nang', AdminLevel: 4, ParentId: '1234' }, { Id:'111213', Name: 'District - Quan 1', AdminLevel: 6, ParentId: '5678' }, { Id:'111213', Name: 'District - Quan 2', AdminLevel: 6, ParentId: '5678' }, { Id:'111213', Name: 'District - Quan 3', AdminLevel: 6, ParentId: '5678' }, { Id:'111213', Name: 'District - Tinh nao do Ha Noi 1', AdminLevel: 6, ParentId: '91011' }, { Id:'111213', Name: 'District - Tinh nao do Ha Noi 2', AdminLevel: 6, ParentId: '91011' }, { Id:'111213', Name: 'District - Tinh nao do Ha Noi 3', AdminLevel: 6, ParentId: '91011' } ];

function group(arr) {
  let root;
  
  //keep a reference of all elements visited
  const lookup = new Map();
  
  //go through the array
  for (const item of arr) {
    //add to the lookup
    const key = item.Id;
    const value = lookup.get(key) ?? [];
    lookup.set(key, value.concat(item));
    
    if ("ParentId" in item){
      //find the parent(s) and update
      lookup.get(item.ParentId)
        .forEach(parent => {
          parent.Data = parent.Data ?? [];
          parent.Data.push(item);
        });
    } else {
      //if the item doesn't have a parent, it's the root node
      root = item;
    }
  }
  
  return root;
}

const result = group(data);

console.log(result);

如果您的数据没有排序并且父母可能在他们的孩子之前或之后出现,则上述代码将不起作用,因为它只会按顺序访问并找到父母,例如,对于[{Id: 2, ParentId: 1}, {Id: 1}],它将失败。如果发生这种情况,您可以将函数的主体更改为首先索引所有项目,然后然后将它们添加到各自的父项:

const data = [ { Id: '1234', Name: 'Country - Viet Nam', AdminLevel: 2 }, { Id:'5678', Name: 'Province - Ho Chi Minh', AdminLevel: 4, ParentId: '1234' }, { Id:'91011', Name: 'Province - Ha Noi', AdminLevel: 4, ParentId: '1234' }, { Id:'111213', Name: 'Province - Da Nang', AdminLevel: 4, ParentId: '1234' }, { Id:'111213', Name: 'District - Quan 1', AdminLevel: 6, ParentId: '5678' }, { Id:'111213', Name: 'District - Quan 2', AdminLevel: 6, ParentId: '5678' }, { Id:'111213', Name: 'District - Quan 3', AdminLevel: 6, ParentId: '5678' }, { Id:'111213', Name: 'District - Tinh nao do Ha Noi 1', AdminLevel: 6, ParentId: '91011' }, { Id:'111213', Name: 'District - Tinh nao do Ha Noi 2', AdminLevel: 6, ParentId: '91011' }, { Id:'111213', Name: 'District - Tinh nao do Ha Noi 3', AdminLevel: 6, ParentId: '91011' } ];

function group(arr) {
  let root;
  
  //keep a reference of all elements visited
  const lookup = new Map();
  
  //first index all items
  for (const item of arr) {
     //add to the lookup
    const key = item.Id;
    const value = lookup.get(key) ?? [];
    lookup.set(key, value.concat(item));
  }
  
  //go through the array
  for (const item of arr) {
    if ("ParentId" in item){
      //find the parent(s) and update
      lookup.get(item.ParentId)
        .forEach(parent => {
          parent.Data = parent.Data ?? [];
          parent.Data.push(item);
        });
    } else {
      //if the item doesn't have a parent, it's the root node
      root = item;
    }
  }
  
  return root;
}

const result = group(data);

console.log(result);

这仍然是一个O(n) 解决方案,因为它会随着输入线性增长。

请注意,这不是按AdminLevel 分组的,因为似乎没有必要。数据自然基于ParentId 分组。首先按AdminLevel 分组是一个无关的操作,因为您不希望每个父级存在多个组。

使用jcalz提供的相同算法查看a TypeScript solution

【讨论】:

  • 您应该明确表明您不是在尝试“按AdminLevel 分组”,尽管对于给定的数据,这自然会发生。
  • @jcalz 当然,我会将其添加到答案中。
  • 我也在研究这个,我认为my solution和你的算法一样(所以我不会发布)
  • @jcalz 谢谢,我希望您不介意将解决方案添加到答案中。另外,如何创建到 TS 游乐场的短链接?
  • 是的,没关系。 Playground 有一个 Link Shortener 插件img
猜你喜欢
  • 2021-03-29
  • 1970-01-01
  • 1970-01-01
  • 2021-02-23
  • 1970-01-01
  • 2021-11-19
  • 1970-01-01
  • 2018-08-30
  • 1970-01-01
相关资源
最近更新 更多