繁体   English   中英

层次排序问题的更好解决方案(JavaScript)

[英]A better solution to hierarchy sorting question (JavaScript)

我得到了这个挑战。

const hierarchy = [
  { memberId: 1, parentMemberId: 8, level: 2, name: 'John Doe' },
  { memberId: 2, parentMemberId: 1, level: 3, name: 'Daniel Thorpe' },
  { memberId: 3, parentMemberId: 5, level: 3, name: 'David Suarez' },
  { memberId: 4, parentMemberId: 5, level: 3, name: 'Felix Mcgee' },
  { memberId: 5, parentMemberId: 8, level: 2, name: 'Deena Duarte' },
  { memberId: 6, parentMemberId: 3, level: 4, name: 'Ron Gaines' },
  { memberId: 7, parentMemberId: 9, level: 5, name: 'Kellie Clements' },
  { memberId: 8, parentMemberId: 0, level: 1, name: 'Tony Soprano' },
  { memberId: 9, parentMemberId: 3, level: 4, name: 'John Kavanagh' },
  { memberId: 10, parentMemberId: 8, level: 2, name: 'Shawn Huynh' },
];

使用这些数据,我应该逐级打印层次结构。 结果示例:

Tony Soprano
John Doe -> Tony Soprano
Deena Duarte -> Tony Soprano
Shawn Huynh -> Tony Soprano
Daniel Thorpe -> John Doe -> Tony Soprano

我是这样做的:

 const printHierarchy = hierarchy => { hierarchy.sort((memberA, memberB) => memberA.level - memberB.level); const hierarchyObj = {}; for (let member of hierarchy) { const { name, memberId, parentMemberId } = member; hierarchyObj[memberId] = name; if (hierarchyObj[parentMemberId] != undefined) { hierarchyObj[memberId] += ` -> ${hierarchyObj[parentMemberId]}`; } } for (let member of hierarchy) { const { memberId } = member; console.log(hierarchyObj[memberId]); } } const hierarchy = [ { memberId: 1, parentMemberId: 8, level: 2, name: 'John Doe' }, { memberId: 2, parentMemberId: 1, level: 3, name: 'Daniel Thorpe' }, { memberId: 3, parentMemberId: 5, level: 3, name: 'David Suarez' }, { memberId: 4, parentMemberId: 5, level: 3, name: 'Felix Mcgee' }, { memberId: 5, parentMemberId: 8, level: 2, name: 'Deena Duarte' }, { memberId: 6, parentMemberId: 3, level: 4, name: 'Ron Gaines' }, { memberId: 7, parentMemberId: 9, level: 5, name: 'Kellie Clements' }, { memberId: 8, parentMemberId: 0, level: 1, name: 'Tony Soprano' }, { memberId: 9, parentMemberId: 3, level: 4, name: 'John Kavanagh' }, { memberId: 10, parentMemberId: 8, level: 2, name: 'Shawn Huynh' }, ]; printHierarchy(hierarchy);
 .as-console-wrapper { min-height: 100%!important; top: 0; }

经理说这很好用,但补充说:

“如果数组包含 100,000 个元素,你还会使用迭代解决方案吗?如果不是,你会怎么做?”

我真的找不到更好的方法。 我错过了什么? 我们确实需要循环排序。

您可以构建一棵树,然后打印该树的所有名称。

这种方法不需要排序数据。

 const getTree = (data, id, parent, children, root) => { const t = {}; data.forEach(o => ((t[o[parent]] ??= {})[children] ??= []).push(Object.assign(t[o[id]] ??= {}, o))); return t[root][children]; }, print = (parents = []) => ({ name, children = [] }) => { const p = [name, ...parents]; console.log(p.join(' -> ')); children.forEach(print(p)); }, hierarchy = [{ memberId: 1, parentMemberId: 8, level: 2, name: 'John Doe' }, { memberId: 2, parentMemberId: 1, level: 3, name: 'Daniel Thorpe' }, { memberId: 3, parentMemberId: 5, level: 3, name: 'David Suarez' }, { memberId: 4, parentMemberId: 5, level: 3, name: 'Felix Mcgee' }, { memberId: 5, parentMemberId: 8, level: 2, name: 'Deena Duarte' }, { memberId: 6, parentMemberId: 3, level: 4, name: 'Ron Gaines' }, { memberId: 7, parentMemberId: 9, level: 5, name: 'Kellie Clements' }, { memberId: 8, parentMemberId: 0, level: 1, name: 'Tony Soprano' }, { memberId: 9, parentMemberId: 3, level: 4, name: 'John Kavanagh' }, { memberId: 10, parentMemberId: 8, level: 2, name: 'Shawn Huynh' }], tree = getTree(hierarchy, 'memberId', 'parentMemberId', 'children', 0); tree.forEach(print()); console.log(tree);
 .as-console-wrapper { max-height: 100% !important; top: 0; }

下一个提供的方法主要基于单个sortmap任务。

在第一个中间步骤中,将为基于memberId的成员项创建基于Map的查找。

然后创建一个已排序的成员项目列表,该列表已经类似于最终成员优先级,因为它按属性level (顶级类别)和memberId (第二级类别)比较和排序成员项目。

最终的map ping 任务将成员项目的排序列表(对于每个项目)迭代到基于字符串的分层有序成员名称图中。 它通过为每个项目聚合相关的分层成员名称列表来实现这一点,同时查找总是下一个父成员,直到找不到父成员。

 function getSortedListOfMemberHirarchyGraphs(memberList) { // create a `memberId` based map of member items for looking it up. const memberLookup = new Map( memberList.map(item => [item.memberId, item]) ); return Array // 1) compare and sort hierarchy levels descending. // create shallow copy of `memberList` in order to not mutate it. .from(memberList) // - top level category: `level` // - 2nd level category: `memberId` .sort((a, b) => (a.level - b.level) || (a.memberId - b.memberId)) // 2) map sorted list of member items ... .map(({parentMemberId, name}) => { let nameList = [name]; let parentMember; // 2a) ... while aggregating for each member's name ... while (parentMember = memberLookup.get(parentMemberId)) { parentMemberId = parentMember.parentMemberId; // 2b) ... a list of related hierarchical member names ... nameList.push(parentMember.name); } // 2c) ... into a graph of hierarchical member names. return nameList.join(' => '); }); } const hierarchy = [ { memberId: 1, parentMemberId: 8, level: 2, name: 'John Doe' }, { memberId: 2, parentMemberId: 1, level: 3, name: 'Daniel Thorpe' }, { memberId: 3, parentMemberId: 5, level: 3, name: 'David Suarez' }, { memberId: 4, parentMemberId: 5, level: 3, name: 'Felix Mcgee' }, { memberId: 5, parentMemberId: 8, level: 2, name: 'Deena Duarte' }, { memberId: 6, parentMemberId: 3, level: 4, name: 'Ron Gaines' }, { memberId: 7, parentMemberId: 9, level: 5, name: 'Kellie Clements' }, { memberId: 8, parentMemberId: 0, level: 1, name: 'Tony Soprano' }, { memberId: 9, parentMemberId: 3, level: 4, name: 'John Kavanagh' }, { memberId: 10, parentMemberId: 8, level: 2, name: 'Shawn Huynh' }, ]; console.log( getSortedListOfMemberHirarchyGraphs(hierarchy) ); console.log( getSortedListOfMemberHirarchyGraphs(hierarchy) .join('\n') ); getSortedListOfMemberHirarchyGraphs(hierarchy) .forEach(graph => console.log(graph));
 .as-console-wrapper { min-height: 100%!important; top: 0; }

上面的实现再次没有评论......

function getSortedListOfMemberHirarchyGraphs(memberList) {
  const memberLookup = new Map(
    memberList.map(item => [item.memberId, item])
  );
  return Array
    .from(memberList)
    .sort((a, b) => (a.level - b.level) || (a.memberId - b.memberId))
    .map(({parentMemberId, name}) => {

      let nameList = [name];
      let parentMember;

      while (parentMember = memberLookup.get(parentMemberId)) {
        parentMemberId = parentMember.parentMemberId;

        nameList.push(parentMember.name);
      }
      return nameList.join(' => ');
    });
}

上述方法与下一个提供的第二次代码重构不会映射两次(创建查找并映射列表); 相反,它直接reduce了分层排序的成员列表,该列表允许成员查找的编程聚合(创建成员项的名称路径所必需的聚合)。

 function getSortedListOfMemberHirarchyGraphs(memberList) { function collectMemberNameGraph(collector, item) { const { memberLookup, result } = collector; let { memberId, parentMemberId, name } = item; memberLookup.set(memberId, item); const nameList = [name]; let parentMember; while (parentMember = memberLookup.get(parentMemberId)) { parentMemberId = parentMember.parentMemberId; nameList.push(parentMember.name); } result.push(nameList.join(' => ')); return collector; } return memberList // skip creation of a shallow `memberList` // copy and don't care about `sort` mutation. .sort((a, b) => (a.level - b.level) || (a.memberId - b.memberId)) .reduce(collectMemberNameGraph, { memberLookup: new Map, result: [], }).result; } const hierarchy = [ { memberId: 1, parentMemberId: 8, level: 2, name: 'John Doe' }, { memberId: 2, parentMemberId: 1, level: 3, name: 'Daniel Thorpe' }, { memberId: 3, parentMemberId: 5, level: 3, name: 'David Suarez' }, { memberId: 4, parentMemberId: 5, level: 3, name: 'Felix Mcgee' }, { memberId: 5, parentMemberId: 8, level: 2, name: 'Deena Duarte' }, { memberId: 6, parentMemberId: 3, level: 4, name: 'Ron Gaines' }, { memberId: 7, parentMemberId: 9, level: 5, name: 'Kellie Clements' }, { memberId: 8, parentMemberId: 0, level: 1, name: 'Tony Soprano' }, { memberId: 9, parentMemberId: 3, level: 4, name: 'John Kavanagh' }, { memberId: 10, parentMemberId: 8, level: 2, name: 'Shawn Huynh' }, ]; console.log( getSortedListOfMemberHirarchyGraphs(hierarchy) ); console.log( getSortedListOfMemberHirarchyGraphs(hierarchy) .join('\n') ); getSortedListOfMemberHirarchyGraphs(hierarchy) .forEach(graph => console.log(graph));
 .as-console-wrapper { min-height: 100%!important; top: 0; }

这是一个完全产生所需输出 OP 的解决方案。 它建立在以下假设之上:

  • hierarchy数组中每个对象的memberId属性实际上是多余的,因为它直接对应于数组中对象的index+1
  • parentMemberId==0表示该元素没有父级。

 const h = hierarchy = [{ memberId: 1, parentMemberId: 8, level: 2, name: 'John Doe' }, { memberId: 2, parentMemberId: 1, level: 3, name: 'Daniel Thorpe' }, { memberId: 3, parentMemberId: 5, level: 3, name: 'David Suarez' }, { memberId: 4, parentMemberId: 5, level: 3, name: 'Felix Mcgee' }, { memberId: 5, parentMemberId: 8, level: 2, name: 'Deena Duarte' }, { memberId: 6, parentMemberId: 3, level: 4, name: 'Ron Gaines' }, { memberId: 7, parentMemberId: 9, level: 5, name: 'Kellie Clements' }, { memberId: 8, parentMemberId: 0, level: 1, name: 'Tony Soprano' }, { memberId: 9, parentMemberId: 3, level: 4, name: 'John Kavanagh' }, { memberId: 10, parentMemberId: 8, level: 2, name: 'Shawn Huynh' }]; function add2Array(el, arr = []) { const [k, v] = Object.entries(el)[0]; arr.push(k) if (typeof v == "object") add2Array(v, arr) return arr } function ancestors(h) { h.forEach(el => { if (el.parentMemberId) el.parent = h[el.parentMemberId - 1] }) h.forEach(el => { el[el.name] = el.parent || ""; ["memberId", "parentMemberId", "level", "name", "parent"].forEach(p => delete el[p]); }); return h.map(e => add2Array(e)).sort((a, b) => a.length - b.length || a[0].localeCompare(b[0])); } const res = ancestors(hierarchy); console.log(res.map(e =>e.join("->")).join("\n"));
 .as-console-wrapper { max-height: 100% !important; top: 0; }

我会选择将其分解一下。 首先,我会将该平面列表转换为一棵树(或者实际上是一个森林,因为不能保证有一个根。)然后我会对结构进行广度优先扫描,将节点及其祖先捕获到一个大批。 然后我的主要功能将提取名称并反转每个祖先,在节点之间添加箭头。 这是一个版本:

 const nest = (xs, id = 0) => xs .filter ((x) => x .parentMemberId == id) .map (({memberId, parentMemberId, children = nest (xs, memberId), ...rest}) => ({ memberId, ...rest, ... (children .length ? {children} : {}) })) const breadthFirst = (xs) => xs .length == 0 ? [] : [ ... xs .map (x => [x]), ... xs .flatMap (x => breadthFirst (x .children || []) .map (ns => [x, ...ns])) ] const display = (xs) => breadthFirst (nest (xs)) .map (xs => xs .map (x => x .name) .reverse () .join (' --> ')) .join ('\n') const hierarchy = [{memberId: 1, parentMemberId: 8, level: 2, name: 'John Doe'}, {memberId: 2, parentMemberId: 1, level: 3, name: 'Daniel Thorpe'}, {memberId: 3, parentMemberId: 5, level: 3, name: 'David Suarez'}, {memberId: 4, parentMemberId: 5, level: 3, name: 'Felix Mcgee'}, {memberId: 5, parentMemberId: 8, level: 2, name: 'Deena Duarte'}, {memberId: 6, parentMemberId: 3, level: 4, name: 'Ron Gaines'}, {memberId: 7, parentMemberId: 9, level: 5, name: 'Kellie Clements'}, {memberId: 8, parentMemberId: 0, level: 1, name: 'Tony Soprano'}, {memberId: 9, parentMemberId: 3, level: 4, name: 'John Kavanagh'}, {memberId: 10, parentMemberId: 8, level: 2, name: 'Shawn Huynh'}] console .log (display (hierarchy))

在这里, nest会将您的输入变成如下内容:

[
  {memberId: 8, level: 1, name: "Tony Soprano", children: [
    {memberId: 1, level: 2, name: "John Doe", children: [
      {memberId: 2, level: 3, name: "Daniel Thorpe"}
    ]},
    {memberId: 5, level: 2, name: "Deena Duarte", children: [
      {memberId: 3, level: 3, name: "David Suarez", children: [
        {memberId: 6, level: 4, name: "Ron Gaines"},
        {memberId: 9, level: 4, name: "John Kavanagh", children: [
          {memberId: 7, level: 5, name: "Kellie Clements"}
        ]}
      ]},
      {memberId: 4, level: 3, name: "Felix Mcgee"}
    ]},
    {memberId: 10, level: 2, name: "Shawn Huynh"}
  ]}
]

然后一个非常通用的breadthFirst将其转换为

[
  [{name: "Tony Soprano", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "John Doe", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "Deena Duarte", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "Shawn Huynh", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "John Doe", /* ... */}, {name: "Daniel Thorpe", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "Deena Duarte", /* ... */}, {name: "David Suarez", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "Deena Duarte", /* ... */}, {name: "Felix Mcgee", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "Deena Duarte", /* ... */}, {name: "David Suarez", /* ... */}, {name: "Ron Gaines", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "Deena Duarte", /* ... */}, {name: "David Suarez", /* ... */}, {name: "John Kavanagh", /* ... */}],
  [{name: "Tony Soprano", /* ... */}, {name: "Deena Duarte", /* ... */}, {name: "David Suarez", /* ... */}, {name: "John Kavanagh", /* ... */},{name: "Kellie Clements", /* ... */}]
]

最后, display只是将这些数组转换为简单的箭头分隔(反转)的名称列表。

可以在另一个答案中描述的更通用的forest函数之上构建nest ,如下所示:

const forest = (build, isChild, root) => (xs) => 
  xs .filter (x => isChild (root, x))
     .map (node => build (node, root => forest (build, isChild, root) (xs)))
    
const nest = forest (
  ({memberId, parentMemberId, ...rest}, f) => ({memberId, parentMemberId, ...rest, children: f (memberId)}),
  (id, x) => x .parentMemberId == id,
  0
)

暂无
暂无

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

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