簡體   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