简体   繁体   English

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

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

I got this challenge.我得到了这个挑战。

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' },
];

Using this data, I should print the hierarchy by level.使用这些数据,我应该逐级打印层次结构。 Result example:结果示例:

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

I did it this way:我是这样做的:

 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; }

The manager said this works great, but added:经理说这很好用,但补充说:

"If the array would contain 100,000 elements, would you still use the iterative solution? if not, what would you do?" “如果数组包含 100,000 个元素,你还会使用迭代解决方案吗?如果不是,你会怎么做?”

I can't really find a better way.我真的找不到更好的方法。 What am I missing?我错过了什么? We do need to loop to sort.我们确实需要循环排序。

You could build a tree and then print all names of the tree.您可以构建一棵树,然后打印该树的所有名称。

This approach does not need sorted data.这种方法不需要排序数据。

 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; }

The next provided approach is based mainly on a single sort and a map task.下一个提供的方法主要基于单个sortmap任务。

In a first intermediate step one would create a Map based lookup for memberId based member items.在第一个中间步骤中,将为基于memberId的成员项创建基于Map的查找。

Then one creates a sorted list of member items which already resemble the final member precedence for it compares and sorts member items by both properties, level (top level category) and memberId (2nd level category).然后创建一个已排序的成员项目列表,该列表已经类似于最终成员优先级,因为它按属性level (顶级类别)和memberId (第二级类别)比较和排序成员项目。

The final map ping task iterates the sorted list of member items, for each item, into a string based graph of hierarchical ordered member names.最终的map ping 任务将成员项目的排序列表(对于每个项目)迭代到基于字符串的分层有序成员名称图中。 It does so by aggregating, for each item, a list of related hierarchical member names while looking up the always next parent member until no parent is/was found.它通过为每个项目聚合相关的分层成员名称列表来实现这一点,同时查找总是下一个父成员,直到找不到父成员。

 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; }

The above implementation again without comments ...上面的实现再次没有评论......

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(' => ');
    });
}

The above approach with the next provided 2nd code refactoring does not map twice (creating the lookup and mapping the list);上述方法与下一个提供的第二次代码重构不会映射两次(创建查找并映射列表); it instead directly reduce s the hierarchically ordered member list which allows for the programmatic aggregation of the member lookup (the one necessary for creating a member item's name-path).相反,它直接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; }

Here is a solution that produces exactly the output OP wanted.这是一个完全产生所需输出 OP 的解决方案。 It builds on the following assumptions:它建立在以下假设之上:

  • the memberId property of each object in the hierarchy array is actually redundant as it corresponds directly with the index+1 of the object in the array, hierarchy数组中每个对象的memberId属性实际上是多余的,因为它直接对应于数组中对象的index+1
  • a parentMemberId==0 signals there is no parent to that element. 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; }

I would choose to break this down a bit.我会选择将其分解一下。 First, I would convert that flat list into a tree (or really a forest, as there is no guarantee of a single root.) Then I would do a breadth-first scan of the structure, capturing the node along with its ancestors into an array.首先,我会将该平面列表转换为一棵树(或者实际上是一个森林,因为不能保证有一个根。)然后我会对结构进行广度优先扫描,将节点及其祖先捕获到一个大批。 Then my main function would extract the names and reverse each ancestry, adding the arrows between nodes.然后我的主要功能将提取名称并反转每个祖先,在节点之间添加箭头。 Here's one version:这是一个版本:

 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))

Here nest will turn your input into something like this:在这里, 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"}
  ]}
]

And then a very generic breadthFirst will convert that to然后一个非常通用的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", /* ... */}]
]

Finally, display just converts those arrays to simple arrow-separated (reversed) lists of names.最后, display只是将这些数组转换为简单的箭头分隔(反转)的名称列表。

nest could be built instead atop a more generic forest function as described in another answer , like this:可以在另一个答案中描述的更通用的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