Corredor

ウェブ、プログラミングの勉強メモ。

MacOS 版 Excel VBA で Dir() 関数の代わり・ファイル一覧を取得する

前回紹介したとおり、Mac 版の Excel VBA では、Dir() 関数がまともに動かない。特に Dir() 関数でファイルの一覧を取得するような処理が全く動かず、Windows 版とは違うコードを書かないといけない。

色々調べてみると、Excel VBA から AppleScript を実行できるので、AppleScript からシェルスクリプトを実行するのが良さそうだ。

試した環境は、MacOS High Sierra、Excel for Mac 2016 (v15.41)

実際のコード

実行する AppleScript はこんな感じ。

do shell script "find -E '/path/to/directory' -type f -iregex '.*.xlsx' -maxdepth 1"

中身はシェルスクリプト。find コマンドを使って、指定のディレクトリ配下にある、.xlsx ファイルのフルパスを取得する、というモノ。

コレを VBA に組み込んで実行してやる。

' 検索対象のディレクトリ
Dim targetPath As String: targetPath = "/path/to/directory"

' AppleScript を組み立てる
Dim appleScript As String
appleScript = "do shell script ""find -E '" & targetPath & "' -type f -iregex '.*.xlsx' -maxdepth 1"""

' 実行結果を格納する変数
Dim result As String: result = ""

' ファイルが一つもないなど、AppleScript の実行結果でエラーが起こるとエラーイベントが発火するので流す
On Error Resume Next
result = MacScript(appleScript)
On Error GoTo 0

' 改行コード CR で区切り、配列にする
Dim filePaths() As String
filePaths = Split(result, vbCr)

' 1件もデータがない場合は UBound(filePaths) は -1、配列でないモノとみなされるので、空の配列に直す
If UBound(filePaths) < 0 Then
  ReDim filePaths(0)
End If

' あとは filePaths をループして使うなり…
' そのまま Workbooks.Open() も呼べる
Workbooks.Open(filePaths(0))

AppleScript を VBA 中で組み立てるところが、可読性が大きく損なわれる。ココは仕方ないか…。

複数行の AppleScript を書きたければ、Chr(13) (= vbCr) を連結することで改行込みのスクリプトが書ける。

今回、'.*.xlsx' という拡張子の絞り込みや、--maxdepth オプションなどは固定で組み込んでしまったが、ココらへんも引数に応じて文字列結合したりしなかったりしても良いだろう。

' AppleScript 中に出てくるダブルクォートを Chr(34) にした
' -iregex を変更して、.xls・.xlsx・.xlsm ファイルがヒットするようにした
appleScript = "do shell script " & Chr(34) & "find -E '" & targetDirectoryPath & "' -type f -iregex '.*.[xls|xlsx|xlsm]' -maxdepth 1" & Chr(34)

find コマンドが Operation not permitted を返すことがある

まだ発生条件やタイミングがよく分かっていなのだが、上述の MacScript() の実行に失敗して、VBA マクロのエラー 5「プロシージャの呼び出し、または引数が不正です。」が発生することがある。

AppleScript 中のシェルスクリプトに 2>&1 を追記したりして調べてみると、find: : Operation not permitted. エラーが発生していることがある。

確かに、何かの拍子に、マクロを実行中に「このディレクトリやファイルへのアクセス権を付与するか?」みたいなダイアログが出てくることはあるのだが、アクセス権を付与したはずのディレクトリでもマクロ入りブックを再起動したりすると発生したりしなかったりで、タイミングが良く分からなかった。

アクセス権の付与ダイアログが表示されるタイミングが分からず色々難儀だったので、自分の場合は、AppleScript の choose folder を使って、先に find したいディレクトリをユーザに選択させることにした。こうすると上手くアクセス権の付与ダイアログが出てくれることが増えた気がした。

' AppleScript のランタイムエラーを逃がすため指定する
On Error Resume Next

' アクセス権を得たいのでディレクトリ選択ダイアログを表示し、ユーザに選択させる
Dim targetDirectoryPath As String
targetDirectoryPath = MacScript("choose folder as string")

If Err.Number <> 0 Then
  ' キャンセルされたか AppleScript のランタイムエラーの場合。Exit しておくと良い
End If

' このままだとコロン区切りのパスになっているので、AppleScript を使ってスラッシュを使う POSIX なパスに変換してもらう
targetDirectoryPath = MacScript("tell text 1 thru -2 of " & Chr(34) & targetDirectoryPath & Chr(34) & " to return quoted form of it's POSIX Path")
' 結果がシングルクォートで囲まれた文字列になっているので、用途に応じて削っておいたり
targetDirectoryPath = Replace(targetDirectoryPath, "'", "")

' find コマンドで検索する
Dim appleScript As String: appleScript = "do shell script " & Chr(34) & "find -E '" & targetDirectoryPath & "' -type f -iregex '.*.[xls|xlsx|xlsm]' -maxdepth 1" & Chr(34)
Dim result As String: result = MacScript(appleScript)

If Err.Number <> 0 Then
  ' シェルスクリプトが異常終了の終了ステータスを返した場合は AppleScript のランタイムエラー扱いになり、エラー 5 としてこのブロックに入る
  ' find コマンドで Operation not permitted エラーが発生したときもココ
End If

On Error GoTo 0

' 取得したファイル一覧を見る
Debug.Print result

こんな感じ。

参考にした文献

今回のコードは以下の文献より参考にした。

Excel ブックに組み込み済みのモノが Zip ファイルで展開されているので、コードを読みやすいよう Gist に転載させていただいた。

グローバル変数 MyFiles に結果を返すようにしている他、Application.Version15 未満の MacOS は AppleScript の書き方が異なる。少し古い Excel for Mac での動作検証はしていないので、上述の簡素化したコードは環境によっては動かないかもしれない。

あとは以前紹介した OS 判定コードと組み合わせて、「Mac では AppleScript を使ってファイル一覧を取得する」「Windows では Dir() 等を使ってファイル一覧を取得する」という風に作ればよかろう。ただ、Dir() 関数はフルパスを返さないことに注意。フルパスを返すよう文字列結合して、配列を返すようにすると、どちらの OS でも同じように関数を使えるようになるだろう。

ちなみに MkDir() は…

ちなみに、MkDir() は、パスをスラッシュ / 区切りで書いてやれば上手くいった。ディレクトリ単体の存在チェックなら Mac でも Dir() 関数が使えたので、以下のようなコードで動作した。

' 起点とするディレクトリ
Dim targetDirectoryPath As String: targetDirectoryPath = "/Users/Neo/work"
' このディレクトリの下に作りたいディレクトリ名
Dim newDirectoryName As String: newDirectoryName = "test"
' パス区切り文字 : ココでは Mac 向けのスラッシュとして定義しておく
Dim pathSplitter As String: pathSplitter = "/"

' 指定のディレクトリ (ココでは /Users/Neo/work/ 配下に test/ ディレクトリ) が存在するかどうか
Dim pathExists As String
' Mac では Dir() の第2引数で vbDirectory の指定が必要
pathExists = Dir(targetDirectoryPath & pathSplitter & newDirectoryName, vbDirectory)  

' なければ test/ ディレクトリを作る
If pathExists = "" Then
  MkDir targetDirectoryPath & pathSplitter & newDirectoryName
End If

その他参考文献

その他、Mac 版 Excel VBA における Dir() 関数に関する参考文献。

Mac の Excel VBA は色々と挙動が違うので、VBA で OS 判別する

最近 Mac で Excel VBA を書く機会があるのだが、Windows の Excel との挙動の違いが多くてつらい。

Excel for Mac のバージョンごとに改善していっているようだが、Excel for Mac 2016 (v15.41) 時点だとこんな感じ。

  • Dir() 関数でファイル一覧をサクッと取得できない
    • Dir("/path/to/directory", MacID("XLSX")) とか Dir("/path/to/directory", vbDirectory) みたいにしないと何も返ってこない。また、Dir() を複数回呼んでのループ (同ディレクトリの複数ファイル取得) もできない
  • パスの区切り文字がバックスラッシュ \ ではなくスラッシュ /
  • VBEditor が重い・日本語入力できない
    • 日本語はコードやコメント中に含めない方が良いらしい。一応コピペすれば書けるが…
    • うまくスクロールしなかったり、とにかく苛立つ挙動

特に、最初に書いた Dir() 関数がまともに動かないというのは、Excel VBA でバッチ処理を作る際に致命的で、既存のマクロの修正を余儀なくされた。

Dir() 関数の代わりとなる、Mac 専用の VBA コードはあるので別途紹介するが、問題は「Windows でも Mac でも、処理の仕方は違えど、結果は同じにしたい」という時に、どうやって OS を判定して処理を分岐させるか、という点である。

調べてみると、Application.OperatingSystem が OS 名を返すので、コレを使うのが楽そうだった。

If Application.OperatingSystem Like "*Mac*" Then
  ' Mac 向けの処理
Else
  ' Windows 向けの処理
End If

例えば、環境に応じてパスの区切り文字が欲しい場合は、こんなコードが作れる。

Function detectPathSplitter() As String
  If Application.OperatingSystem Like "*Mac*" Then
    detectPathSplitter = "/"
  Else
    detectPathSplitter = Chr(92)  ' Backslash
  End If
End Function

バックスラッシュ \ をそのまま書かず、Chr(92) としているのは、Mac の VBEditor でマクロを保存すると、マクロ中に書かれたバックスラッシュが消えてしまうため。コメント内に書いてあるバックスラッシュまで消えてしまうので難儀だった。

それだけネックだったが、ひとまずは以前紹介した、Bash スクリプトの中で OS 判定するようなノリで判定できた。

neos21.hatenablog.com

ひと目でわかるOffice 2016 for Mac

ひと目でわかるOffice 2016 for Mac

MacOS の Finder でファイルをゴミ箱に入れず直接削除するには

Windows エクスプローラだと、Shift + Delete でゴミ箱を経由せず直接ファイル削除ができるが、MacOS の Finder だとやり方はないものか。

調べたところ、Cmd + Option + Delete と押せば良いことが分かった。

ついつい Windows の癖で「Shift で動作が変わるのかなー」と思ってしまいがちなのだが、Mac の場合は Option キーで動作が変わることの方が多いな。ファイルの切り取り・貼り付けも、

  • Windows は Ctrl + XCtrl + V だが、
  • Mac だと Cmd + C (コピーと同じ) → Cmd + Option + V

だもんなぁ。

Option キー、覚えましたし。

macOS Mojaveマスターブック (Mac Fan Books)

macOS Mojaveマスターブック (Mac Fan Books)

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引数に渡した関数の、仮引数 ab は、隣り合う要素を持ってきていることが分かる。コレに対し、比較の結果、-101 のいずれかを return してやることで、並び順が変えられる。上述の例では ab より小さい時に 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…」始まりで上手くソートされているのが分かる。

以上

このように、複数のキー (条件) を使ってのソートも、比較的簡単に実装できることが分かった。

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

  • 作者: Ethan Brown,武舎広幸,武舎るみ
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2017/01/20
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る