平面数组重组为树数组

Posted

技术标签:

【中文标题】平面数组重组为树数组【英文标题】:flat array restructure to a tree array 【发布时间】:2020-08-19 22:19:31 【问题描述】:

我一直在尝试将以下平面数组(参与,见下文)重组为更有条理的树形式,以便我可以使用同步融合的树网格组件。我试过使用 .reduce() 函数。但似乎我无法做出正确的结构。我也尝试过 lodash 按唯一 ID 对它们进行分组。无论如何,这是这个平台上的某个人帮助前进的: 起始阵列参与如下。 一些属性的名称也需要重命名。

//what ive tried so far 
const custommodifier = (participations) => participations.reduce((a,KlasCode, LESDatum, LESID, Moduleomschrijving,ParticipationLetterCode) => 

    if (a[KlasCode] )
      if (a[ParticipationLetterCode] )
      a[KlasCode].subtasks[0].subtasks[0].subtasks.push(
          // ParticipationLetterCode,
          taskName: LESDatum,
          LESID,
        )
       else 
        // a[KlasCode].subtasks[0].subtasks[0].taskName = ParticipationLetterCode
        a[KlasCode].subtasks[0].subtasks.push(
            taskName: ParticipationLetterCode,
            subtasks: [
              taskName: LESDatum,
            ]
        )
      
     else 
      a[KlasCode] = 
        taskName: KlasCode,
        subtasks: [
          taskName:Moduleomschrijving,
          subtasks: [
            taskName: ParticipationLetterCode,
            subtasks: [
              // ParticipationLetterCode,
              taskName: LESDatum,
              LESID,
            ]
          ]
        ]
      
    
    return a;
, );

您可以在下面找到自定义函数应使其看起来的正确数据结构。 谢谢看到这个的人

//starting point
let participations = [
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "12/12/20",
    LESID: "1",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "X"
  , 
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "11/11/20",
    LESID: "2",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "X",
  ,
  
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "1/1/20",
    LESID: "3",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "Y"
  ,
  
    KlasCode: "2S RD BJ RR ma-d",
    LESDatum: "5/12/20",
    LESID: "4",
    ModuleID: "1051",
    Moduleomschrijving:"Realisaties shirts",
    ParticipationLetterCode: "Z"
  ,
  
    KlasCode: "2S RD BJ RR ma-d",
    LESDatum: "6/11/20,
    LESID: "4",
    ModuleID: "1051",
    Moduleomschrijving:"Realisaties shirts",
    ParticipationLetterCode: "Z"
  
]

// Need to make the data look like this including field name change: 
let participations = [
  "taskName": "1S RD BJ GS ma-d",
  "subtasks": [
    "ModuleID": "1050",
    "taskName": "Realisaties blouse/jurk",
    "subtasks": [
        "taskName": "X",
        "subtasks": [
            "taskName": "12/12/20",
            "LESID": "1",
          ,
          
            "taskName": "11/11/20",
            "LESID": "2",
          
        ],
      ,
      
        "taskName": "Y",
        "subtasks": [
          "taskName": "1/1/20",
          "LESID": "3",
        ]
      
    ]

  ]
,

  "taskName": "2S RD BJ RR ma-d",
  "subtasks": [
    "ModuleID": "1051",
    "taskName": "Realisaties shirts",
    "subtasks": [
        "taskName": "Z",
        "subtasks": [
            "taskName": "5/12/20",
            "LESID":"4"
          ,
          
            "taskName": "6/11/20",
            "LESID":"5"
          
        ],
      
    ]
  ]
]

【问题讨论】:

【参考方案1】:

您可以与另一个数组进行分组。

let data = [ KlasCode: "1S RD BJ GS ma-d", LESDatum: "12/12/20", LESID: "1", ModuleID: "1050", Moduleomschrijving: "Realisaties blouse/jurk", ParticipationLetterCode: "X" ,  KlasCode: "1S RD BJ GS ma-d", LESDatum: "11/11/20", LESID: "2", ModuleID: "1050", Moduleomschrijving: "Realisaties blouse/jurk", ParticipationLetterCode: "X" ,  KlasCode: "1S RD BJ GS ma-d", LESDatum: "1/1/20", LESID: "3", ModuleID: "1050", Moduleomschrijving: "Realisaties blouse/jurk", ParticipationLetterCode: "Y" ,  KlasCode: "2S RD BJ RR ma-d", LESDatum: "5/12/20", LESID: "4", ModuleID: "1051", Moduleomschrijving: "Realisaties shirts", ParticipationLetterCode: "Z" ,  KlasCode: "2S RD BJ RR ma-d", LESDatum: "6/11/20", LESID: "4", ModuleID: "1051", Moduleomschrijving: "Realisaties shirts", ParticipationLetterCode: "Z" ],
    groups = [['KlasCode'], ['Moduleomschrijving', 'ModuleID'], ['ParticipationLetterCode'], ['LESDatum']],
    result = data
        .reduce((r, o) => 
            groups.reduce((p, [key, ...levelKeys]) => 
                let taskName = o[key],
                    temp = (p.subtasks = p.subtasks || []).find(q => q.taskName === taskName);
                if (!temp) 
                    let moreProps = levelKeys.reduce((t, k) => ( ...t, [k]: o[k] ), );
                    p.subtasks.push(temp =  ...moreProps, taskName );
                
                return temp;
            , r)
            return r;
        ,  subtasks: [] )
        .subtasks;

console.log(result);
.as-console-wrapper  max-height: 100% !important; top: 0; 

【讨论】:

快到了!最内部的对象如何(例如:结果[0].subtasks[0].subtasks[0].subtasks[0].LESDatum):如何将“LESDatum”属性名称更改为“taskName”【参考方案2】:

创建一个class Task 并让它管理它的子任务。 将原始数组上的 reducer 与 Task 类结合使用来创建所需的结构。 检查stackblitz链接以获取解决方案 https://stackblitz.com/edit/js-vvxkve

class Task 
    constructor ( taskName ) 
        this.taskName = taskName;
        this.subtasks = [];
    
    addSubTask ( options ) 
        const ModuleID = options['ModuleID'] || null;
        const taskName = options['Moduleomschrijving'] || null;
        const participationLetterCode = options['ParticipationLetterCode'] || null;
        const subTask = this.subtasks.find ( s => s.ModuleID === ModuleID );
        const subTaksL2 = 
            taskName: options['LESDatum'] || null,
            LESID: options['LESID'] || null
         
        if ( !subTask ) 
            subTask = 
                ModuleID,
                taskName,
                subtasks: [
                    taskName: participationLetterCode,
                    subtasks: [ subTaksL2 ]
                ]
            
            this.subtasks.push ( subTask );
         else 
            let subTaskL1 = subTask.subtasks.find ( s => s.taskName === participationLetterCode );
            if ( !subTaskL1 ) 
                subTaskL1 = 
                    taskName: participationLetterCode,
                    subtasks: []
                
                subTask.subtasks.push ( subTaskL1 );
             
            subTaskL1.subtasks.push ( subTaksL2 );
        
    


let participations = [
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "12/12/20",
    LESID: "1",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "X"
  , 
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "11/11/20",
    LESID: "2",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "X",
  ,
  
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "1/1/20",
    LESID: "3",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "Y"
  ,
  
    KlasCode: "2S RD BJ RR ma-d",
    LESDatum: "5/12/20",
    LESID: "4",
    ModuleID: "1051",
    Moduleomschrijving:"Realisaties shirts",
    ParticipationLetterCode: "Z"
  ,
  
    KlasCode: "2S RD BJ RR ma-d",
    LESDatum: "6/11/20",
    LESID: "4",
    ModuleID: "1051",
    Moduleomschrijving:"Realisaties shirts",
    ParticipationLetterCode: "Z"
  
];

participations = participations.reduce ( ( acc, cval ) => 
      const taskName = cval['KlasCode'] || null;
      let node = acc.find ( a => a.taskName === taskName );
      if ( !node ) 
          node = new Task ( taskName );
          acc.push ( node );
      
      node.addSubTask ( cval );
      return acc;
, []);

【讨论】:

【参考方案3】:

这是一个非常有趣的问题。

我喜欢 Nina Scholz 的总体思路,但我真的想要一个更通用的版本。我想出的是一个功能,配置如下:

[
  _children: 'subtasks', taskName: 'KlasCode',
  _children: 'subtasks', taskName: 'Moduleomschrijving', ModuleID: 'ModuleID',
  _children: 'subtasks', taskName: 'ParticipationLetterCode',
  taskName: 'LESDatum',
]

(如果我在这个问题上花费更多时间,请参阅下面的代码,了解如何更改此配置。)

这表示输出的外层从KlasCode 属性中获取一个名为taskName 的属性,对所有匹配的值进行分组,并将其命名为子数组subtasks。这些孩子从Moduleomschrijving 获得taskName,从ModuleID 获得ModuleID,也将其孩子命名为subtasks,等等。最后一个节点将名称 LESDatum 转换为 taskName,但没有子节点可以下降。所有剩余的名称都保持不变。我假设ModuleomschrijvingModuleID 总是同步的。如果这不是真的,那么我可能会遗漏一些重要的东西。

实现依赖于两个辅助函数:

groupBy 将一个数组转换为一个对象,其中包含您的自定义函数的结果以及生成该键的原始元素的值数组。 omit 创建一个缺少给定键的对象的副本。

许多实用程序库中都提供了此类功能。我们还有两个主要功能。

nestGroup:采用其中一个配置对象和一组对象,进行键转换、属性重命名和子嵌套。这本身就是一个有用的功能,如果您只有一层嵌套,这很有用。

nestGroups:使用提供的第一个级别调用 nestGroup,并递归调用 nestGroups 并使用子数组上的其余配置级别。当没有剩余级别时,它会触底,并原封不动地返回数组。

最后,最后一个函数被柯里化了,所以我们可以创建一个可重用的函数来嵌入我们的配置,并将数组作为参数。这对 OP 可能有用也可能没用,但我可以看到它在其他地方有用。我们通过调用

来利用这一点
const nestParticipations = nestGroups (config)
// ... later
const tree = nestParticipations (participations)

但我们也可以这样做

const tree = nestGroups (config) (participations)

您可以在这里看到它的实际效果:

const groupBy = (fn) => (xs) => 
  xs .reduce((a, x) => (... a, [fn(x)]: [... (a [fn (x)] || []), x]), )

const omit = (keys) => (obj) => 
  Object .fromEntries (Object .entries (obj) .filter (([k, v]) => !keys.includes(k)))

const nestGroup = (level) => 
  const _children, ...rest = level
  const keys = Object .values (rest)
  const pairs = Object .entries (rest)
  return (xs) => 
    Object .values (groupBy (x => keys .map (k => x [k]) .join ('|')) (xs))
      .map (group => (
        ... (Object .assign (... (pairs .map (([k, v]) => ([k]: group [0] [v] ))))),
        ... (_children ? [_children]: group .map (omit (keys)) : ... omit (keys) (group [0]))
      ))


const nestGroups = ([level = undefined, ... levels]) => (xs) =>
  level == undefined
    ? xs
    : nestGroup (level) (xs)  
        .map (([level._children]: childGroup, ... rest) => (
            ... rest, 
            ... (childGroup ? [level._children]: nestGroups (levels) (childGroup) : )
        )) 

const config = [
  _children: 'subtasks', taskName: 'KlasCode',
  _children: 'subtasks', taskName: 'Moduleomschrijving', ModuleID: 'ModuleID',
  _children: 'subtasks', taskName: 'ParticipationLetterCode',
  taskName: 'LESDatum',
]

const nestParticipations = nestGroups (config)

let participations = [ KlasCode: "1S RD BJ GS ma-d", LESDatum: "12/12/20", LESID: "1", ModuleID: "1050", Moduleomschrijving:"Realisaties blouse/jurk", ParticipationLetterCode: "X" ,  KlasCode: "1S RD BJ GS ma-d", LESDatum: "11/11/20", LESID: "2", ModuleID: "1050", Moduleomschrijving:"Realisaties blouse/jurk", ParticipationLetterCode: "X" ,  KlasCode: "1S RD BJ GS ma-d", LESDatum: "1/1/20", LESID: "3", ModuleID: "1050", Moduleomschrijving:"Realisaties blouse/jurk", ParticipationLetterCode: "Y" ,  KlasCode: "2S RD BJ RR ma-d", LESDatum: "5/12/20", LESID: "4", ModuleID: "1051", Moduleomschrijving:"Realisaties shirts", ParticipationLetterCode: "Z" ,  KlasCode: "2S RD BJ RR ma-d", LESDatum: "6/11/20", LESID: "4", ModuleID: "1051", Moduleomschrijving:"Realisaties shirts", ParticipationLetterCode: "Z"  ]

console .log (
  nestParticipations (participations)
)
.as-console-wrapper min-height: 100% !important; top: 0

如果我想在这方面花更多时间,我想我会进一步分解它,我可能会使用更像这样的配置:

[
   children: 'subtasks', matchOn: [ 'KlasCode' ], rename:  KlasCode: 'taskName'  ,
  
    children: 'subtasks', 
    matchOn: [ 'Moduleomschrijving', 'ModuleID' ], 
    rename:  Moduleomschrijving: 'taskName' 
  ,
  
    children: 'subtasks', 
    matchOn: [ 'ParticipationLetterCode' ],
    rename:  ParticipationLetterCode: 'taskName' 
  ,
   rename: LESDatum, 'taskName' 
]

留给读者作为练习......

【讨论】:

【参考方案4】:

我们检查了您的数组结构,为了将 FlatArray 转换为 TreeGrid 数组结构,需要定义 Mapping Field 以形成父子层次结构。因此我们建议您使用定义字段(如下面的 ParentId)来形成 TreeGrid 结构。

在 TreeGrid 组件中使用自引用数据绑定(平面数据)时,需要为层次关系定义 IdMapping 和 ParentIdMapping 属性。 参考代码示例:-

让 treeGridObj: TreeGrid = new TreeGrid( 数据来源:参与, idMapping: 'LESID', parentIdMapping: 'ParentId', 允许分页:真, 树列索引:1, 列: [ field: 'LESID', headerText: 'Task ID', width: 90, textAlign: 'Right' , 字段:'ParticipationLetterCode',headerText:'任务名称',宽度:180 , . . . ] );

参考下面的TreeGrid DataSourceArray 结构:-

让参与 = [ KlasCode: "1S RD BJ GS ma-d", LESDatum:“20 年 12 月 12 日”, 莱斯德:1, 模块ID:“1050”, Moduleomschrijving:“Realisaties blouse/jurk”, 参与字母代码:“X”, ParentId = null

    ,  
        KlasCode: "1S RD BJ GS ma-d", 
        LESDatum: "11/11/20", 
        LESID: 2, 
        ModuleID: "1050", 
        Moduleomschrijving: "Realisaties blouse/jurk", 
        ParticipationLetterCode: "X", 
        ParentId = 1                               //  Here ParentId(ParentIdMapping) value with 1 has been grouped under LESID(IdMapping) with Value 1 
    , 
     
        KlasCode: "1S RD BJ GS ma-d", 
        LESDatum: "1/1/20", 
        LESID: 3, 
        ModuleID: "1050", 
        Moduleomschrijving: "Realisaties blouse/jurk", 
        ParticipationLetterCode: "Y", 
        ParentId = 1 
    , 
     
        KlasCode: "2S RD BJ RR ma-d", 
        LESDatum: "5/12/20", 
        LESID: 4, 
        ModuleID: "1051", 
        Moduleomschrijving: "Realisaties shirts", 
        ParticipationLetterCode: "Z", 
        ParentId = null 
    , 
     
        KlasCode: "2S RD BJ RR ma-d", 
        LESDatum: "6/11/20", 
        LESID: 5, 
        ModuleID: "1051", 
        Moduleomschrijving: "Realisaties shirts", 
        ParticipationLetterCode: "Z", 
        ParentId = 4 
     
    ] 

ID 字段:此字段包含用于标识节点的唯一值。它的名称被分配给 idMapping 属性。 父 ID 字段:此字段包含指示父节点的值。其名称分配给 parentIdMapping 属性。

参考文档和演示链接:- https://ej2.syncfusion.com/demos/#/material/tree-grid/selfreference.html https://ej2.syncfusion.com/documentation/treegrid/data-binding/#self-referential-data-binding-flat-data

如果您需要任何进一步的帮助,请与我们联系

问候, 法文苏丹娜 T

【讨论】:

以上是关于平面数组重组为树数组的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地检索树中节点的路径(与帖子“将平面表解析为树?”相关)

尝试从 Excel 2013 批量导入到 TFS 时,如何将我的平面列表转换为树列表?

从平面对象数组构建对象树数组[重复]

将层次数组转换为平面数组

将平面对象数组转换为嵌套对象数组[重复]

将平面数组转换为分层的多维数组