【问题标题】:Sort array of objects based on best matching another array根据最佳匹配另一个数组对对象数组进行排序
【发布时间】:2021-12-23 18:37:11
【问题描述】:

我有一个字符串数组,可以看作是模板/参考:

// Template
let template = ['A', 'B', 'C'];

我还有一个包含一些数组字符串的对象数组:

// Source (to be sorted)
let source = [
   { items: ['B', 'D', 'E'] },
   { items: ['E', 'L', 'Y'] },
   { items: ['G', 'B', 'A'] },
   { items: ['C', 'B', 'A'] }
];

我现在需要找到一种方法,通过匹配template 数组(从最佳到最差)对source 数组中的items 进行排序。 items 中的字符串顺序无关。

根据我上面的例子,排序后的结果是这样的:

// Result after sorting
let sorted = [
   { items: ['C', 'B', 'A'] }, // 3 matches (A, B, C)
   { items: ['G', 'B', 'A'] }, // 2 matches (A, B)
   { items: ['B', 'D', 'E'] }, // 1 match (B)
   { items: ['E', 'L', 'Y'] } // no match
];

使用 JavaScript 方法 .sort() 和 .localCompare() 我可以根据字符串对数组进行排序,但我找不到按给定数组“模板”排序的方法。

如果有人知道如何做到这一点,我会非常高兴? 提前谢谢!

【问题讨论】:

  • source.map(a => a.items.filter(b => template.includes(b)).sort()) 给你。
  • @Ankit 是的,那行不通。
  • 有兴趣知道为什么不@RobbyCornelissen
  • @Ankit 您是否将您的解决方案生成的输出与问题中发布的预期输出进行了比较?显然不是。
  • 对我来说工作正常,确保你不要忘记更改变量名:p @RobbyCornelissen

标签: javascript arrays sorting


【解决方案1】:

您的逻辑可以基于Set 的大小,其中包含来自templateitems 的不同元素。 Set越小,越常见:

const template = ['A', 'B', 'C'];

const source = [
   { items: ['B', 'D', 'E'] },
   { items: ['E', 'L', 'Y'] },
   { items: ['G', 'B', 'A'] },
   { items: ['C', 'B', 'A'] }
];

const target = source
  .map(({items}) => ({items, size: new Set([...items, ...template]).size}))
  .sort((a, b) => a.size - b.size);

console.log(target);

如果您不处理数组中的不同值,这可能更合适:

const template = ['A', 'B', 'C'];

const source = [
   { items: ['B', 'D', 'E'] },
   { items: ['E', 'L', 'Y'] },
   { items: ['G', 'B', 'A'] },
   { items: ['C', 'B', 'A'] }
];

const target = source
  .map(({items}) => ({
    items,
    size: items.reduce((a, v) => a + template.includes(v), 0)
  }))
  .sort((a, b) => b.size - a.size);

console.log(target);

这可以通过将template 转换为Set 来进一步优化。

【讨论】:

  • 这有一个缺点,就是用剩余的size 属性“污染”输出数据,这甚至不是他所说的匹配数。但也许 OP 确实喜欢这样(因为你不需要重新计算它)。
  • @Drarig29 是的,但可以很容易地用另外一个map()forEach()delete 将它们过滤掉。与sort() 操作中的每个元素比较计算相比,计算只进行一次(就像其他答案提出的那样)。
  • 我正在处理不同的价值观,所以你的第一种方法对我来说非常好,谢谢!我在我的代码中修改了您的解决方案,因为我的源数组有更多级别,但为了简单起见,我在示例中删除了它们。工作得很好!
【解决方案2】:

您只需要构建一个适当的排序函数来比较 2 个数组元素。例如,您可以计算与您的模板匹配的项目。

function sortByMatch(template) {
  const items = new Set(template)
  
  const rank = arr => arr.filter(item => items.has(item)).length
  
  return (a, b) => rank(b.items) - rank(a.items)
}

let template = ['A', 'B', 'C'];

let source = [
   { items: ['B', 'D', 'E'] },
   { items: ['E', 'L', 'Y'] },
   { items: ['G', 'B', 'A'] },
   { items: ['C', 'B', 'A'] }
];

const sorted = source.slice().sort(sortByMatch(template))

console.log(sorted.map(item => item.items.join(', ')))

【讨论】:

  • 这里有一个主要缺点:rank 函数被执行 12 次来对这个小数组进行排序,并且这个计数会随着数组的增长呈指数增长。
  • @RobbyCornelissen 嗯,是的,但是“过早的优化是万恶之源”
  • 真的! :)