繁体   English   中英

对对象数组进行排序,然后按 id 分组(JavaScript)

[英]Sort array of objects, then group by id (JavaScript)

我有一个需要一些非常规排序的对象数组。 每个对象包含一个id字符串和一个num int。 一些未排序的虚拟数据:

[{"id":"ABC","num":111},
{"id":"DEF","num":130},
{"id":"XYZ","num":115},
{"id":"QRS","num":98},
{"id":"DEF","num":119},
{"id":"ABC","num":137},
{"id":"LMN","num":122},
{"id":"ABC","num":108}]

我需要升序由NUM -但是,如果一个ID出现不止一次,该ID在位置应该“飘起来”的其他记录驻留其兄弟下方具有其次最小NUM。

最终结果将是:

[{"id":"QRS","num":98},
{"id":"ABC","num":108},
{"id":"ABC","num":111},
{"id":"ABC","num":137},
{"id":"XYZ","num":115},
{"id":"DEF","num":119},
{"id":"DEF","num":130},
{"id":"LMN","num":122}]

实际数组可能包含 15k+ 条记录,因此将不胜感激任何有效的解决方案。 带有一些嵌套“ifs”的.sort(function(a,b) {...})可以很好地实现基本排序,但我对“浮动”逻辑感到困惑。 提前致谢。

编辑:到目前为止我所拥有的(基本嵌套排序):

const sortedData = origData.sort(function(a, b) {
  if (a.num === b.num) {
    if (a.id === b.id) {
      return a.id.localeCompare(b.id);
    }
  }
  return a.num - b.num;
});

一种方法是

  • 按 id 分组的第一组
  • 然后按 num 对每个组进行排序
  • 然后按 min(num) 对组进行排序
  • 然后连接组

 let data = [{"id":"ABC","num":111}, {"id":"DEF","num":130}, {"id":"XYZ","num":115}, {"id":"QRS","num":98}, {"id":"DEF","num":119}, {"id":"ABC","num":137}, {"id":"LMN","num":122}, {"id":"ABC","num":108}]; const groupById = (acc, item) => { const id = item.id; if(id in acc){ acc[id].push(item); }else{ acc[id] = [item]; } return acc; }; const sortByNum = (a,b) => a.num - b.num; const sortByMinNum = (a,b) => a[0].num - b[0].num; const groups = Object.values(data.reduce(groupById, {})) .map(group => group.sort(sortByNum)) .sort(sortByMinNum); console.log([].concat(...groups));
 .as-console-wrapper{top:0;max-height:100%!important}

另一种方法是

  • 首先通过 id 确定最小数量
  • 然后先按 minNum 和 num 排序

 let data = [{"id":"ABC","num":111}, {"id":"DEF","num":130}, {"id":"XYZ","num":115}, {"id":"QRS","num":98}, {"id":"DEF","num":119}, {"id":"ABC","num":137}, {"id":"LMN","num":122}, {"id":"ABC","num":108}]; const minNumById = data.reduce((acc, item) => { const id = item.id; if(id in acc){ acc[id] = Math.min(acc[id], item.num); }else{ acc[id] = item.num; } return acc; }, {}); data.sort((a, b) => minNumById[a.id] - minNumById[b.id] || a.num - b.num); console.log(data);
 .as-console-wrapper{top:0;max-height:100%!important}

编辑:哇。 两年后看这段代码很奇怪。

阅读这些片段后,我意识到第二种方法存在缺陷。 如果多个 ID 具有相同的minNum则代码可能会混合这些块,就好像它们是相同的 ID。
修复,如果这是您的数据问题:

data.sort((a, b) => minNumById[a.id] - minNumById[b.id] || a.id.localeCompare(b.id) || a.num - b.num);

minNum排序,然后按id排序,然后按num排序。

但是回到更新的原因:

选择一个而不是另一个的原因是什么

从技术上讲,第一种方法可能会生成大量中间对象(尤其是具有大量 ID 且每个 ID 条目很少的情况),但整体排序可能会更快,因为它正在对较小的列表进行排序。
而第二种方法应该不那么浪费内存。

但是在常规设备上两者都不重要,除非列表变得庞大; 你必须用你的具体数据来测试它是否有任何理由在这里优化。

更重要的是:作为使用代码的开发人员,您应该对此感到满意。 除非这里有实际的性能瓶颈,否则你应该选择你觉得更舒服、更容易理解和浏览的方法。
削减几微秒与创建错误相比,因为您不了解所使用的代码以及调试/修复该代码所需的时间。 更重要的是什么?

这是我想出的。 您需要首先按 id 分组并将分组的 id 存储到数组中。 然后,按num asc排序并考虑任何分组的 id:

编辑:修复分组 id 的asc排序

 var data = [{"id":"ABC","num":111}, {"id":"DEF","num":130}, {"id":"XYZ","num":115}, {"id":"QRS","num":98}, {"id":"DEF","num":119}, {"id":"ABC","num":137}, {"id":"LMN","num":122}, {"id":"ABC","num":108}]; const sortArray = arr => { let matchingIds = []; const sorted = arr.sort( (a,b) => { if(a.id === b.id){ matchingIds.push(a.id); return 0; }else{ return 1; } }).sort( (a,b) => { if(matchingIds.indexOf(a.id) > -1 && matchingIds.indexOf(b.id) > -1 && a.id === b.id) { return a.num - b.num; } if(matchingIds.indexOf(a.id) > -1 || matchingIds.indexOf(b.id) > -1) { return 0; } return a.num - b.num; }); console.log(sorted); } sortArray(data);

  • 我首先创建了一张地图。

  • 该地图基本上将id作为键,并将其所有值放在一个数组中。

  • 对地图的每个键的单个数组进行排序。

  • 现在,将所有这些收集到一个新的对象集合中,再次对它们进行排序, only first比较only first元素。

  • 现在,只需遍历新集合并将它们推送到结果数组中。

 var collection = [ { "id": "ABC", "num": 111 }, { "id": "DEF", "num": 130 }, { "id": "XYZ", "num": 115 }, { "id": "QRS", "num": 98 }, { "id": "DEF", "num": 119 }, { "id": "ABC", "num": 137 }, { "id": "LMN", "num": 122 }, { "id": "ABC", "num": 108 } ]; var map = {}; for (var i = 0; i < collection.length; ++i) { if (map[collection[i].id] === undefined) { map[collection[i].id] = []; } map[collection[i].id].push(collection[i].num); } var new_collection = []; for (var key in map) { map[key].sort(function(a, b) { return a - b; }); var new_obj = {}; new_obj[key] = map[key]; new_collection.push(new_obj); } new_collection.sort(function(a, b) { var key1 = Object.keys(a)[0]; var key2 = Object.keys(b)[0]; return a[key1][0] - b[key2][0]; }); var result = []; for (var i = 0; i < new_collection.length; ++i) { var curr_obj = new_collection[i]; var curr_key = Object.keys(curr_obj)[0]; for (var j = 0; j < curr_obj[curr_key].length; ++j) { var new_obj = {}; new_obj['id'] = curr_key; new_obj['num'] = curr_obj[curr_key][j]; result.push(new_obj); } } console.log(result);

我是函数式编程库Ramda 的忠实粉丝。 (免责声明:我是它的作者之一。)我倾向于从简单的、可重用的功能方面考虑。

当我想到如何解决这个问题时,我是通过 Ramda 的观点来考虑的。 我可能会像这样解决这个问题:

 const {pipe, groupBy, prop, map, sortBy, values, head, unnest} = R; const transform = pipe( groupBy(prop('id')), map(sortBy(prop('num'))), values, sortBy(pipe(head, prop('num'))), unnest ) const data = [{"id": "ABC", "num": 111}, {"id": "DEF", "num": 130}, {"id": "XYZ", "num": 115}, {"id": "QRS", "num": 98}, {"id": "DEF", "num": 119}, {"id": "ABC", "num": 137}, {"id": "LMN", "num": 122}, {"id": "ABC", "num": 108}] console.log(transform(data))
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>

我认为这是相当可读的,至少一旦你理解了管道创建了一个函数管道,每个函数将其结果传递给下一个函数。

现在,通常没有理由包含像 Ramda 这样的大型库来解决相当简单的问题。 但是该版本中使用的所有功能都可以轻松重用。 因此,尝试创建这些函数的您自己的版本并让它们对您的应用程序的其余部分可用可能是有意义的。 事实上,这就是像 Ramda 这样的库的实际构建方式。

因此,这是一个具有这些功能的简单实现的版本,您可以将它们放在实用程序库中:

 const groupBy = (fn) => (arr) => arr.reduce((acc, val) => (((acc[fn(val)] || (acc[fn(val)] = [])).push(val)), acc), {}) const head = (arr) => arr[0] const mapObj = (fn) => (obj) => Object.keys(obj).reduce((acc, val) => (acc[val] = fn(obj[val]), acc), {}) const pipe = (...fns) => (arg) => fns.reduce((a, f) => f(a), arg) const prop = (name) => (obj) => obj[name] const values = Object.values const unnest = (arr) => [].concat(...arr) const sortBy = (fn) => (arr) => arr.slice(0).sort((a, b) => { const aa = fn(a), bb = fn(b) return aa < bb ? -1 : aa > bb ? 1 : 0 }) const transform = pipe( groupBy(prop('id')), mapObj(sortBy(prop('num'))), values, sortBy(pipe(head, prop('num'))), unnest ) const data = [{"id": "ABC", "num": 111}, {"id": "DEF", "num": 130}, {"id": "XYZ", "num": 115}, {"id": "QRS", "num": 98}, {"id": "DEF", "num": 119}, {"id": "ABC", "num": 137}, {"id": "LMN", "num": 122}, {"id": "ABC", "num": 108}] console.log(transform(data))

暂无
暂无

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

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