【问题标题】:How to reduce unsorted array against another sorted array, keeping sorted order?如何减少未排序数组对另一个排序数组,保持排序顺序?
【发布时间】:2022-01-10 19:08:32
【问题描述】:

给定一个名为allItems 的对象数组,它是预先排序的,但不能从它包含的信息中再次排序 - 下面的reduce 函数的替代实现是什么,它将保留allItems 的排序顺序?

下面的逻辑会输出:

[{ id: 'd' }, { id: 'a' }, { id: 'b' }]

所需的输出是:

[{ id: 'a' }, { id: 'b' }, { id: 'd' }]
// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];

const includedIds = ['d', 'a', 'b'];

// QUESTION: How to create the same output, but in the order they appear in allItems
const unsortedIncludedItems = includedIds.reduce((accumulator, id) => {
  const found = allItems.find(n => n.id === id);
  if (found) accumulator.push(found);
  return accumulator;
}, [])

正如在回复@Ben 时提到的,出于性能原因,简单地颠倒逻辑会破坏交易。

【问题讨论】:

  • 你的尝试是什么?你试过什么?
  • 我在概念化阶段因为笨拙而放弃了所有想法,并转向互联网寻找建议,然后在我失败时求助于 StackOverflow。
  • 如果我理解您所写的内容,您的代码将完全向后输出。所以你可以在之后反转列表。或者,您可以推到最前面(注意,这在普通 JS 列表中效率较低,您可能必须实现自己的队列)
  • @Ben 这种方法的问题在于allItems 可能包含数千个项目,而includedIds 可能有十几个 - 而allItems.find() 将在找到匹配项时停止。简单地反转它就可以了,除非它会对性能造成巨大的影响。抱歉,应该从一开始就提到这一点。
  • 出于性能原因,仅仅颠倒逻辑会破坏交易” - 不知道你所说的“颠倒逻辑”是什么意思?

标签: javascript arrays sorting ecmascript-6


【解决方案1】:

为什么不直接反转逻辑,过滤掉不应该包含的id。

// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [
  { id: "a" },
  { id: "b" },
  { id: "c" },
  { id: "d" },
  { id: "e" },
  { id: "f" },
];

const includedIds = ["d", "a", "b"];

const findElms = (items, includedIds) => items.filter((n) => includedIds.includes(n.id))

console.log(findElms(allItems, includedIds));

【讨论】:

    【解决方案2】:

    更新

    示例 B 将使用输入数组的索引对过滤后的数组进行排序。

    尝试.filter().include() 获取所需的对象,然后通过每个对象的字符串值.sort()。请参阅示例 A

    另一种方法是使用.flatMap().include() 获取数组数组。

    // each index is from the original array
    [ [15, {id: 'x'}], [0, {id: 'z'}], [8, {id: 'y'}] ]
    

    然后在每个子数组索引上使用.sort()

    [ [0, {id: 'z'}], [8, {id: 'y'}], [15, {id: 'x'}] ] 
    

    最后,再次使用.flatMap() 提取对象并将数组数组展平为对象数组。

     [ {id: 'z'},  {id: 'y'},  {id: 'x'} ]
    

    参见示例 B

    示例 A(按值排序)

    const all = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
    
    const values = ['d', 'a', 'b'];
    
    const sortByStringValue = (array, vArray, key) => array.filter(obj => vArray.includes(obj[key])).sort((a, b) => a[key].localeCompare(b[key]));
    
    console.log(JSON.stringify(sortByStringValue(all, values, 'id')));

    示例 B(按索引排序)

    const all = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
    const values = ['d', 'a', 'b'];
    
    const alt = [{name:'Matt'}, {name:'Joe'}, {name:'Jane'}, {name:'Lynda'}, {name:'Shelly'}, {name:'Alice'}];
    const filter = ['Shelly', 'Matt', 'Lynda'];
    
    const sortByIndex = (array, vArray, key) => array.flatMap((obj, idx) => vArray.includes(obj[key]) ? [[idx, obj]] : []).sort((a, b) => a[0] - b[0]).flatMap(sub => [sub[1]]);
    
    console.log(JSON.stringify(sortByIndex(all, values, 'id')));
       
    console.log(JSON.stringify(sortByIndex(alt, filter, 'name')));

    【讨论】:

    • OP 说all 是预先排序的,所以不需要最后的sort 方法调用。
    • @jsejcksn OP includedIds 出现故障,因此 sort()
    • 不,对过滤器的结果进行排序是错误的。 OP 仅使用/^[a-z]$/ 作为 ID 的示例,但表示 all 已预先排序,无法从可用信息中确定排序顺序。
    • @jsejcksn 了解,更新了答案 - 请参阅 示例 B
    • @zer00ne 感谢您的回答! Example B 的性能影响似乎类似于将原始 reduce 函数反转为 allItems.reduce(...) 的性能影响 - 我弄错了吗?
    【解决方案3】:

    您遇到的问题是您的代码反转了列表。您可以简单地推到列表的前面,并保持原来的顺序。

    不幸的是,推到列表的前面比较慢,它是 O(n) 而不是 O(1)。看起来Array.prototype.unshift 应该是faster,但根据blog,它仍然是 O(n)。假设找到的元素数量很少,您不会注意到任何性能问题。在这种情况下,将push 替换为unshift,如下所示:

    // NOTE: allItems is pre-sorted, but lacks the information to re-sort it
    const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
    
    const includedIds = ['d', 'a', 'b'];
    
    // QUESTION: How to create the same output, but in the order they appear in allItems
    const unsortedIncludedItems = includedIds.reduce((accumulator, id) => {
      const found = allItems.find(n => n.id === id);
      if (found) accumulator.unshift(found);
      return accumulator;
    }, [])
    

    否则,这些是您的选择:

    1. 围绕这个对象创建一个包装器,它反转索引而不是数组。这可以通过这样的函数来完成:

      const getFromEnd = (arr, i) => arr[arr.length - 1 - i]

    请注意,在新的浏览器版本(最近几个月)中,这可以替换为 arr.at(-i)。如果您倾向于 OOP,可以将其封装在一个类中。

    1. 请记住在使用此数组的任何位置手动反转索引(这很容易出错,因为您可能会忘记反转它们)
    2. 反转数组。如这个fiddle所示,即使有10,000个元素,性能也不错。假设这不是热路径或用户交互代码,我认为即使 100,000 也可能没问题。

    【讨论】:

      【解决方案4】:

      不要遍历includedIds(以错误的顺序)并查看是否可以在allItems 中找到它们,而是遍历allItems(这是正确的顺序)并查看是否可以找到它们的ID在includedIds:

      const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
      
      const includedIds = ['d', 'a', 'b'];
      
      const includedItems = allItems.filter(item => includedIds.includes(item.id));
      

      【讨论】:

      • 对于它的价值,如果性能像 OP 似乎暗示的那样关键,那么将 includedIds 变成一个集合可能是明智的。我想这也取决于它最终有多大。
      • @Ben 我想提一下,但 OP 在评论中写道“includedIds 可能是一打”,所以我认为不是值得
      猜你喜欢
      • 2018-06-14
      • 1970-01-01
      • 2013-11-10
      • 2015-06-08
      • 2011-07-06
      • 2013-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多