こんなデータを特定のキーの値でソートしてランキングを付けたい
const idols = [ {id: 1, name: '星宮いちご', score: 90}, {id: 2, name: '霧矢あおい', score: 83}, {id: 3, name: '紫吹蘭', score: 80}, {id: 4, name: '有栖川おとめ', score: 85}, {id: 5, name: '藤堂ユリカ', score: 87}, {id: 6, name: '神崎美月', score: 92}, {id: 7, name: '夏樹みくる', score: 80}, {id: 8, name: '大空あかり', score: 91}, {id: 9, name: '氷上すみれ', score: 83}, {id: 10, name: '新条ひなき', score: 80}, ];
作戦
score
でソートした配列を作る- 元の配列をループで回して、ソートした配列で同じ
id
を持つアイテムのindex + 1
が順位になる
1. 配列内のオブジェクトのキーでソートする
Array.sort()
にソート順を定義する関数を渡してソートをコントロールする事ができる
e.g.
// ASC 昇順 arr.sort((a, b) => { if ( a === b ) { return 0; } return a < b ? -1 : 1; }); // DESC 降順 arr.sort((a, b) => { if ( a === b ) { return 0; } return a > b ? -1 : 1; });
cf. Array.prototype.sort() - JavaScript | MDN
オブジェクトのキーでソートする場合は、このソート関数の中をオブジェクトのキーで比較するようにすればOK
score キーでソートする関数
const sortByScore = (arr) => (orderBy = 'DESC') => { // array.sort は元の配列を変更してしまうのでコピーしたものを使用する return […arr].sort((a, b) => { if ( a.score === b.score ) { return 0; } if ( orderBy === 'ASC' ) { [a, b] = [b, a]; } return a.score > b.score ? -1 : 1; }); }; console.log( sortByScore(idols)('DESC') ); /* [ { id: 6, name: '神崎美月', score: 92 }, { id: 8, name: '大空あかり', score: 91 }, { id: 1, name: '星宮いちご', score: 90 }, { id: 5, name: '藤堂ユリカ', score: 87 }, { id: 4, name: '有栖川おとめ', score: 85 }, { id: 2, name: '霧矢あおい', score: 83 }, { id: 9, name: '氷上すみれ', score: 83 }, { id: 3, name: '紫吹蘭', score: 80 }, { id: 7, name: '夏樹みくる', score: 80 }, { id: 10, name: '新条ひなき', score: 80 } ] */ console.log( sortByScore(idols)('ASC') ); /* => [ { id: 3, name: '紫吹蘭', score: 80 }, { id: 7, name: '夏樹みくる', score: 80 }, { id: 10, name: '新条ひなき', score: 80 }, { id: 2, name: '霧矢あおい', score: 83 }, { id: 9, name: '氷上すみれ', score: 83 }, { id: 4, name: '有栖川おとめ', score: 85 }, { id: 5, name: '藤堂ユリカ', score: 87 }, { id: 1, name: '星宮いちご', score: 90 }, { id: 8, name: '大空あかり', score: 91 }, { id: 6, name: '神崎美月', score: 92 } ] */
score
が同じ場合は 0
が返るので常に index が先のものが先になるようになっています。
score
が大きい順のときは id
が早い方が先、小さい順のときは id
が後の方が先にしたい場合は条件を次のように変更すればOKです。
const sortByScore = (arr) => (orderby = 'DESC') => { return [...arr].sort((a, b) => { if ( orderby === 'ASC' ) { [a, b] = [b, a]; } return (a.score > b.score || a.score === b.score && a.id < b.id) ? -1 : 1; }); };
2. 元の配列をループしてソートした配列から順位を取り出す
2.1. ソートした配列を id だけのリストに変換する
score
順にソートした配列だと、内部にオブジェクトを持っているので、元配列の id が何番目か探すのが少し面倒です。
単純な Array.indexOf
で順位を取れるように、ソートした配列を id だけの配列に変換します。(ユニークなキーであれば問題ないので name でも別に構わないですが)
const mapDataToKeyArray = (arr, key = 'id') => { return arr.map((item) => { return item[key]; }); }; // score の大きい順に並べたリスト const idolsOrderByScoreDESC = sortByScore(idols)('DESC'); // => [ { id: 6, name: '神崎美月', score: 92 }, { id: 8, name: '大空あかり', score: 91 }, … ] // id だけの配列に変換 mapDataToKeyArray( idolsOrderByScoreDESC ); // => [ 6, 8, 1, 5, 4, 2, 9, 3, 7, 10 ] // name だけの配列に変換 mapDataToKeyArray( idolsOrderByScoreDESC, 'name' ); // => [ '神崎美月', '大空あかり', '星宮いちご', '藤堂ユリカ', … ]
2.3. ソートしたデータを id だけに変換した配列の index から順位をつける
const mapRankingToData = (arr, key = 'id') => (sortedArr) => { const sortedKeys = mapDataToKeyArray(sortedArr, key); const ranking = arr.map((item) => { const id = item[key]; // ランキングが無い場合: array.indexOf(id) =>-1, -1 + 1 => 0 const rank = sortedKeys.indexOf(id) + 1; // 配列内のオブジェクトは参照値になっているので `…item` でコピーする return {...item, ranking: rank || null}; }); return ranking; }; // score の大きい順に並べたリスト const idolsOrderByScoreDESC = sortByScore(idols)('DESC'); // ランキングを追加したデータ const rankedList = mapRankingToData(idols)(idolsOrderByScoreDESC); /* => [ { id: 1, name: '星宮いちご', score: 90, ranking: 3 }, { id: 2, name: '霧矢あおい', score: 83, ranking: 6 }, { id: 3, name: '紫吹蘭', score: 80, ranking: 8 }, { id: 4, name: '有栖川おとめ', score: 85, ranking: 5 }, { id: 5, name: '藤堂ユリカ', score: 87, ranking: 4 }, { id: 6, name: '神崎美月', score: 92, ranking: 1 }, { id: 7, name: '夏樹みくる', score: 80, ranking: 9 }, { id: 8, name: '大空あかり', score: 91, ranking: 2 }, { id: 9, name: '氷上すみれ', score: 83, ranking: 7 }, { id: 10, name: '新条ひなき', score: 80, ranking: 10 } ] */
まとめ
全体のコードをまとめるとこんな感じ
const sortByScore = (arr) => (orderby = 'DESC') => { return [...arr].sort((a, b) => { if ( orderby === 'ASC' ) { [a, b] = [b, a]; } return (a.score > b.score || a.score === b.score && a.id < b.id) ? -1 : 1; }); }; const mapDataToKeyArray = (arr, key = 'id') => { return arr.map((item) => { return item[key]; }); }; const mapRankingToData = (arr, key = 'id') => (sortedArr) => { const sortedKeys = mapDataToKeyArray(sortedArr, key); const ranking = arr.map((item) => { const id = item[key]; const rank = sortedKeys.indexOf(id) + 1; return {...item, ranking: rank || null}; }); return ranking; }; // ランキングを追加したデータ const rankedList = mapRankingToData(idols)(sortByScore(idols)('DESC')); /* => [ { id: 1, name: '星宮いちご', score: 90, ranking: 3 }, { id: 2, name: '霧矢あおい', score: 83, ranking: 6 }, { id: 3, name: '紫吹蘭', score: 80, ranking: 8 }, { id: 4, name: '有栖川おとめ', score: 85, ranking: 5 }, { id: 5, name: '藤堂ユリカ', score: 87, ranking: 4 }, { id: 6, name: '神崎美月', score: 92, ranking: 1 }, { id: 7, name: '夏樹みくる', score: 80, ranking: 9 }, { id: 8, name: '大空あかり', score: 91, ranking: 2 }, { id: 9, name: '氷上すみれ', score: 83, ranking: 7 }, { id: 10, name: '新条ひなき', score: 80, ranking: 10 } ] */
関数を組み合わせる設計にしておけば、配列をfilter する関数を作成して score > 85
のランキングも簡単に作ることができる
const filterScore = (arr) => (limit) => { return arr.filter((item) => { return item.score > limit; }); }; // score > 85 だけのデータを抽出 const overScore85Idols = filterScore(idols)(85); // score > 85 のランキングを追加しデータ const rankedList = mapRankingToData(overScore85Idols)(sortByScore(overScore85Idols)('DESC')); /* => [ { id: 1, name: '星宮いちご', score: 90, ranking: 3 }, { id: 5, name: '藤堂ユリカ', score: 87, ranking: 4 }, { id: 6, name: '神崎美月', score: 92, ranking: 1 }, { id: 8, name: '大空あかり', score: 91, ranking: 2 } ] */
ランキングを付けてランキング順に並べたデータを作ることも簡単です
// score > 85 だけのデータを抽出 const overScore85Idols = filterScore(idols)(85); // score が高い順に並べたリスト const sortedIdols = sortByScore(overScore85Idols)('DESC') // ランキングを追加したランキング順のリスト const rankedList = mapRankingToData(sortedIdols)(sortedIdols); /* => [ { id: 6, name: '神崎美月', score: 92, ranking: 1 }, { id: 8, name: '大空あかり', score: 91, ranking: 2 }, { id: 1, name: '星宮いちご', score: 90, ranking: 3 }, { id: 5, name: '藤堂ユリカ', score: 87, ranking: 4 } ] */
関数楽しい!
何回も配列ぶん回してるから計算量は多そうだけど、疎結合?にするならこんな感じで良いのかな?
つづき
[参考]
- Array.prototype.sort() - JavaScript | MDN
- JavaScript つい忘れてしまう配列のソート方法 - Qiita
- JS:Array内の数値の順位を求める | ARTICLE | goma

- 発売日: 2020/04/02
- メディア: Blu-ray

TVアニメ/データカードダス『アイカツオンパレード! 』OP/EDテーマ 「君のEntrance/アイドル活動! オンパレードver.」
- アーティスト:V.A.
- 発売日: 2019/10/23
- メディア: CD
2020年もアイカツ!を見れば幸せになれる!