平面数组重组为树数组
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
,但没有子节点可以下降。所有剩余的名称都保持不变。我假设Moduleomschrijving
和ModuleID
总是同步的。如果这不是真的,那么我可能会遗漏一些重要的东西。
实现依赖于两个辅助函数:
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
【讨论】:
以上是关于平面数组重组为树数组的主要内容,如果未能解决你的问题,请参考以下文章
如何有效地检索树中节点的路径(与帖子“将平面表解析为树?”相关)