JavaScript の sort() 関数をお勉強 : 複数のプロパティを見てソートする方法
JavaScript の Array.prototype.sort()
を使って、連想配列の複数のプロパティを見てソートする方法を勉強した。
目次
対象データ
対象となるデータは、以下のようなモノ。company
が会社名で、name
がその会社の商品名だと思って欲しい。
const guitars = [
{ company: 'Gibson' , name: 'Les Paul' },
{ company: 'Gibson' , name: 'Les Paul Custom' },
{ company: 'Squire' , name: 'Telecaster' },
{ company: 'Squire' , name: 'Stratocaster' },
{ company: 'Gibson' , name: 'SG Junior' },
{ company: 'Gibson' , name: 'SG Custom' },
{ company: 'Gibson' , name: 'SG' },
{ company: 'Fender' , name: 'Mustang' },
{ company: 'Fender' , name: 'Telecaster Thinline' },
{ company: 'Fender' , name: 'Jazz Master' },
{ company: 'Epiphone', name: 'Les Paul' },
{ company: 'Epiphone', name: 'Les Paul Custom' },
{ company: 'Squire' , name: 'Telecaster Thinline' },
{ company: 'Squire' , name: 'Mustang' },
{ company: 'Squire' , name: 'Jazz Master' },
{ company: 'Epiphone', name: 'SG' },
{ company: 'Fender' , name: 'Stratocaster' },
{ company: 'Fender' , name: 'Telecaster' },
{ company: 'Epiphone', name: 'SG Junior' },
{ company: 'Epiphone', name: 'SG Custom' },
];
エレキギターをかじった人なら分かるかもしれないが、「Gibson」と「Epiphone」、「Fender」と「Squire」は親・子会社 (ブランド) の関係で、例えば「Gibson」と「Epiphone」は同じ「Les Paul」という名前の製品を持っている (今回はあくまで一例なので、詳細なモデル名までは記していない)。
このようなオブジェクトの配列を並び替えて、会社名順 → 製品名順に並び替えたい、というのが今回の趣旨。
sort()
関数の比較ロジックは自作できる
まずは話をもっと単純にして、数値のみが入った配列に対して sort()
を行うと、以下のようにソートしてくれる。
[5, 1, 3, 4, 2].sort();
// → [1, 2, 3, 4, 5]
ココで、sort()
の第1引数に独自の比較ロジックを関数で与えると、並び替えのやり方を変えられる。
[5, 1, 3, 4, 2].sort((a, b) => {
// 何が起こっているのか確認するためコンソール出力してみる
console.log(`A : ${a} ・ B : ${b}`);
// 通常の判定とは逆にしてみる
if(a < b) return 1;
if(a > b) return -1;
return 0;
});
さて、コレを実行すると、以下のようになる。
A : 5 ・ B : 1
A : 1 ・ B : 3
A : 5 ・ B : 3
A : 1 ・ B : 4
A : 3 ・ B : 4
A : 5 ・ B : 4
A : 1 ・ B : 2
A : 3 ・ B : 2
[ 5, 4, 3, 2, 1 ]
sort()
の第1引数に渡した関数の、仮引数 a
と b
は、隣り合う要素を持ってきていることが分かる。コレに対し、比較の結果、-1
・0
・1
のいずれかを return
してやることで、並び順が変えられる。上述の例では a
が b
より小さい時に 1
を返し、その逆は -1
を返すようにしたので、結果が降順ソートになっている。
このように比較関数を渡さない場合は、内部的には要素が文字列やオブジェクトなどであっても <
や >
で大小比較しているので、人間が思うような結果が得られない。そこで、各要素からプロパティを取り出し、その値を比較してソートしてやろう。
あ、ちなみに、Array.prototype.sort()
は、対象の要素を直接並び替える。map()
などのようにコピーした配列を返すワケではなく、元の配列に破壊的変更を加える関数なので留意。
まずは製品名だけでソートする
sort()
関数のカスタマイズということで、まずは製品名だけを見てソートするよう、比較関数を与えてやる。内部の動きが追いやすいよう、今回もコンソール出力させてみる。
const columnify = require('columnify'); // コンソール出力の整形用
// ソートしたいデータ
const guitars = [ /* 前述のとおり・省略 */ ];
// sort() 内の関数が呼ばれた回数をカウントする
let count = 0;
// あとで一括してコンソール出力するため、ソート中のデータを控えておく
let outputs = [];
guitars.sort((a, b) => {
// カウンタのインクリメントとコンソール出力用の控え
count++;
outputs.push({
'Count' : count,
'A:Company': a.company,
'A:Name' : a.name,
'B:Company': b.company,
'B:Name' : b.name
});
// 製品名で比較する
if(a.name < b.name) return -1;
if(a.name > b.name) return 1;
// 同一値なら 0 を返す
return 0;
});
console.log('▼ ソート中のログ');
console.log(columnify(outputs, { columnSplitter: ' | ' }));
console.log('▼ ソート結果');
console.log(guitars);
結果は以下のとおり。
▼ ソート中のログ
COUNT | A:COMPANY | A:NAME | B:COMPANY | B:NAME
1 | Gibson | Les Paul | Epiphone | SG Custom
2 | Gibson | Les Paul | Epiphone | Les Paul
3 | Squire | Telecaster | Gibson | Les Paul
4 | Epiphone | SG Junior | Gibson | Les Paul
5 | Fender | Telecaster | Gibson | Les Paul
6 | Fender | Stratocaster | Gibson | Les Paul
7 | Epiphone | SG | Gibson | Les Paul
8 | Squire | Jazz Master | Gibson | Les Paul
9 | Squire | Stratocaster | Gibson | Les Paul
10 | Squire | Mustang | Gibson | Les Paul
11 | Squire | Telecaster Thinline | Gibson | Les Paul
12 | Epiphone | Les Paul Custom | Gibson | Les Paul
13 | Gibson | Les Paul Custom | Gibson | Les Paul
14 | Fender | Jazz Master | Gibson | Les Paul
15 | Gibson | SG Junior | Gibson | Les Paul
16 | Fender | Telecaster Thinline | Gibson | Les Paul
17 | Fender | Mustang | Gibson | Les Paul
18 | Gibson | SG | Gibson | Les Paul
19 | Gibson | SG Custom | Gibson | Les Paul
20 | Epiphone | Les Paul | Squire | Jazz Master
21 | Epiphone | Les Paul | Fender | Jazz Master
22 | Squire | Jazz Master | Fender | Jazz Master
23 | Gibson | SG Junior | Epiphone | SG Custom
24 | Epiphone | SG Custom | Squire | Telecaster Thinline
25 | Gibson | SG Junior | Squire | Telecaster Thinline
26 | Gibson | SG | Gibson | SG Junior
27 | Fender | Mustang | Gibson | SG Junior
28 | Fender | Telecaster Thinline | Gibson | SG Junior
29 | Epiphone | SG Junior | Gibson | SG Junior
30 | Squire | Stratocaster | Gibson | SG Junior
31 | Fender | Telecaster | Gibson | SG Junior
32 | Fender | Stratocaster | Gibson | SG Junior
33 | Epiphone | SG | Gibson | SG Junior
34 | Gibson | Les Paul Custom | Gibson | SG Junior
35 | Epiphone | Les Paul Custom | Gibson | SG Junior
36 | Gibson | SG Custom | Gibson | SG Junior
37 | Squire | Mustang | Gibson | SG Junior
38 | Squire | Telecaster | Gibson | SG Junior
39 | Squire | Telecaster | Squire | Stratocaster
40 | Squire | Telecaster | Fender | Stratocaster
41 | Squire | Stratocaster | Fender | Stratocaster
42 | Squire | Telecaster | Fender | Telecaster
43 | Fender | Telecaster | Fender | Telecaster Thinline
44 | Fender | Telecaster Thinline | Squire | Telecaster Thinline
45 | Epiphone | SG Custom | Gibson | SG
46 | Epiphone | SG Custom | Fender | Mustang
47 | Gibson | SG | Fender | Mustang
48 | Epiphone | SG Custom | Epiphone | SG
49 | Gibson | SG | Epiphone | SG
50 | Epiphone | SG Custom | Gibson | Les Paul Custom
51 | Epiphone | SG | Gibson | Les Paul Custom
52 | Gibson | SG | Gibson | Les Paul Custom
53 | Fender | Mustang | Gibson | Les Paul Custom
54 | Epiphone | SG Custom | Epiphone | Les Paul Custom
55 | Epiphone | SG | Epiphone | Les Paul Custom
56 | Gibson | SG | Epiphone | Les Paul Custom
57 | Fender | Mustang | Epiphone | Les Paul Custom
58 | Gibson | Les Paul Custom | Epiphone | Les Paul Custom
59 | Epiphone | SG Custom | Gibson | SG Custom
60 | Gibson | SG Custom | Squire | Mustang
61 | Epiphone | SG Custom | Squire | Mustang
62 | Epiphone | SG | Squire | Mustang
63 | Gibson | SG | Squire | Mustang
64 | Fender | Mustang | Squire | Mustang
▼ ソート結果
[
{ company: 'Squire', name: 'Jazz Master' },
{ company: 'Fender', name: 'Jazz Master' },
{ company: 'Epiphone', name: 'Les Paul' },
{ company: 'Gibson', name: 'Les Paul' },
{ company: 'Gibson', name: 'Les Paul Custom' },
{ company: 'Epiphone', name: 'Les Paul Custom' },
{ company: 'Fender', name: 'Mustang' },
{ company: 'Squire', name: 'Mustang' },
{ company: 'Gibson', name: 'SG' },
{ company: 'Epiphone', name: 'SG' },
{ company: 'Epiphone', name: 'SG Custom' },
{ company: 'Gibson', name: 'SG Custom' },
{ company: 'Epiphone', name: 'SG Junior' },
{ company: 'Gibson', name: 'SG Junior' },
{ company: 'Squire', name: 'Stratocaster' },
{ company: 'Fender', name: 'Stratocaster' },
{ company: 'Squire', name: 'Telecaster' },
{ company: 'Fender', name: 'Telecaster' },
{ company: 'Fender', name: 'Telecaster Thinline' },
{ company: 'Squire', name: 'Telecaster Thinline' }
]
長ったらしいが、要素数20個の配列に対し、64回に渡って比較関数が実行され、製品名でソートできた。「ソート結果」を見ると、name
プロパティの値が J・L・M・S・T… から始まる値で並んでいるのが分かるだろう。
複数の項目を見てソートするには?
さて、今回やりたいのは、まず会社名別にソートして、1つの会社内の製品もソートするという要件だ。コレを実現するには、以下のように実装する。
guitars.sort((a, b) => {
// 先にグルーピングしたい「会社名」で比較する
if(a.company < b.company) return -1;
if(a.company > b.company) return 1;
// 会社名が同一の場合、次に「製品名」で比較する
if(a.name < b.name) return -1;
if(a.name > b.name) return 1;
// 同一値なら 0 を返す
return 0;
});
コレを実行すると、以下のようになる。
▼ ソート中のログ
COUNT | A:COMPANY | A:NAME | B:COMPANY | B:NAME
1 | Gibson | Les Paul | Epiphone | SG Custom
2 | Epiphone | SG Custom | Epiphone | Les Paul
3 | Squire | Telecaster | Epiphone | SG Custom
4 | Epiphone | SG Junior | Epiphone | SG Custom
5 | Fender | Telecaster | Epiphone | SG Custom
6 | Fender | Stratocaster | Epiphone | SG Custom
7 | Epiphone | SG | Epiphone | SG Custom
8 | Squire | Stratocaster | Epiphone | SG Custom
9 | Squire | Jazz Master | Epiphone | SG Custom
10 | Squire | Mustang | Epiphone | SG Custom
11 | Squire | Telecaster Thinline | Epiphone | SG Custom
12 | Epiphone | Les Paul Custom | Epiphone | SG Custom
13 | Gibson | SG Junior | Epiphone | SG Custom
14 | Gibson | Les Paul Custom | Epiphone | SG Custom
15 | Fender | Jazz Master | Epiphone | SG Custom
16 | Fender | Telecaster Thinline | Epiphone | SG Custom
17 | Fender | Mustang | Epiphone | SG Custom
18 | Gibson | SG | Epiphone | SG Custom
19 | Gibson | SG Custom | Epiphone | SG Custom
20 | Epiphone | Les Paul | Epiphone | SG
21 | Epiphone | SG | Epiphone | Les Paul Custom
22 | Epiphone | Les Paul | Epiphone | Les Paul Custom
23 | Gibson | SG Junior | Gibson | Les Paul
24 | Gibson | Les Paul | Squire | Telecaster Thinline
25 | Gibson | SG Junior | Squire | Telecaster Thinline
26 | Gibson | SG | Gibson | SG Junior
27 | Fender | Mustang | Gibson | SG Junior
28 | Fender | Telecaster Thinline | Gibson | SG Junior
29 | Fender | Jazz Master | Gibson | SG Junior
30 | Gibson | Les Paul Custom | Gibson | SG Junior
31 | Squire | Stratocaster | Gibson | SG Junior
32 | Epiphone | SG Junior | Gibson | SG Junior
33 | Gibson | SG Custom | Gibson | SG Junior
34 | Squire | Mustang | Gibson | SG Junior
35 | Fender | Telecaster | Gibson | SG Junior
36 | Squire | Jazz Master | Gibson | SG Junior
37 | Fender | Stratocaster | Gibson | SG Junior
38 | Squire | Telecaster | Gibson | SG Junior
39 | Squire | Telecaster | Squire | Jazz Master
40 | Squire | Telecaster | Squire | Mustang
41 | Squire | Jazz Master | Squire | Mustang
42 | Squire | Telecaster | Squire | Stratocaster
43 | Squire | Mustang | Squire | Stratocaster
44 | Squire | Telecaster | Squire | Telecaster Thinline
45 | Gibson | Les Paul | Gibson | SG
46 | Gibson | SG | Fender | Mustang
47 | Gibson | Les Paul | Fender | Mustang
48 | Gibson | SG | Fender | Telecaster Thinline
49 | Gibson | Les Paul | Fender | Telecaster Thinline
50 | Fender | Mustang | Fender | Telecaster Thinline
51 | Gibson | SG | Fender | Jazz Master
52 | Gibson | Les Paul | Fender | Jazz Master
53 | Fender | Telecaster Thinline | Fender | Jazz Master
54 | Fender | Mustang | Fender | Jazz Master
55 | Gibson | SG | Gibson | Les Paul Custom
56 | Gibson | Les Paul | Gibson | Les Paul Custom
57 | Gibson | SG | Epiphone | SG Junior
58 | Gibson | Les Paul Custom | Epiphone | SG Junior
59 | Gibson | Les Paul | Epiphone | SG Junior
60 | Fender | Telecaster Thinline | Epiphone | SG Junior
61 | Fender | Mustang | Epiphone | SG Junior
62 | Fender | Jazz Master | Epiphone | SG Junior
63 | Gibson | SG | Gibson | SG Custom
64 | Gibson | SG Custom | Fender | Telecaster
65 | Gibson | SG | Fender | Telecaster
66 | Gibson | Les Paul Custom | Fender | Telecaster
67 | Gibson | Les Paul | Fender | Telecaster
68 | Fender | Telecaster Thinline | Fender | Telecaster
69 | Fender | Mustang | Fender | Telecaster
70 | Gibson | SG Custom | Fender | Stratocaster
71 | Gibson | SG | Fender | Stratocaster
72 | Gibson | Les Paul Custom | Fender | Stratocaster
73 | Gibson | Les Paul | Fender | Stratocaster
74 | Fender | Telecaster Thinline | Fender | Stratocaster
75 | Fender | Telecaster | Fender | Stratocaster
76 | Fender | Mustang | Fender | Stratocaster
▼ ソート結果
[
{ company: 'Epiphone', name: 'Les Paul' },
{ company: 'Epiphone', name: 'Les Paul Custom' },
{ company: 'Epiphone', name: 'SG' },
{ company: 'Epiphone', name: 'SG Custom' },
{ company: 'Epiphone', name: 'SG Junior' },
{ company: 'Fender', name: 'Jazz Master' },
{ company: 'Fender', name: 'Mustang' },
{ company: 'Fender', name: 'Stratocaster' },
{ company: 'Fender', name: 'Telecaster' },
{ company: 'Fender', name: 'Telecaster Thinline' },
{ company: 'Gibson', name: 'Les Paul' },
{ company: 'Gibson', name: 'Les Paul Custom' },
{ company: 'Gibson', name: 'SG' },
{ company: 'Gibson', name: 'SG Custom' },
{ company: 'Gibson', name: 'SG Junior' },
{ company: 'Squire', name: 'Jazz Master' },
{ company: 'Squire', name: 'Mustang' },
{ company: 'Squire', name: 'Stratocaster' },
{ company: 'Squire', name: 'Telecaster' },
{ company: 'Squire', name: 'Telecaster Thinline' }
]
今度は比較関数が76回呼ばれている (先程は64回)。会社名での比較も入れたので、ソート中の動きが変わっている。
そして結果は、狙ったとおり、会社名別に「E・F・G・S…」始まりで並び、会社内の製品も「L・S…」「J・M・S・T…」始まりで上手くソートされているのが分かる。
以上
このように、複数のキー (条件) を使ってのソートも、比較的簡単に実装できることが分かった。