【JS】辞書として使うなら配列?オブジェクト?Map?様々なパターンで比較
こんにちは!最近記事の更新をtwitterで言うようにしているMizutani(@sirycity)です。webhookとか使って自動ツイートとか作ってみたいね。いつか。
今回はJavaScriptで辞書っぽいのを作る時に配列、オブジェクト、Mapのどれを使うのがいいかって話をします。
結論
- 基本どれでもおk。どれかじゃないとできない処理はほぼない。
- 個人的におすすめなのは配列
- 綺麗なのは配列
- 速いのはMapオブジェクト
- 関数型的な書き方するなら配列
- オブジェクト指向的な書き方するならMapオブジェクト
- 配列は答えが複数個の場合にめっぽう強い。
はじめに
JavaScriptには辞書(Dictionary)はないです。なので配列やらオブジェクトやらMapやら、他の機能を使って代替します。
今回は配列、オブジェクト、Mapオブジェクトの3つに対して色々な処理をしていく様子を比較します。この記事では例としてこのような日本の都道府県名と県庁所在地名のペアを使っていくよ。こんな感じ。
const arrJ = [
['北海道', '札幌市'],
/* 中略 */
['沖縄県', '那覇市'],
]
const objJ = {
北海道: '札幌市',
/* 中略 */
沖縄県: '那覇市',
}
const mapJ = new Map([
['北海道', '札幌市'],
/* 中略 */
['沖縄県', '那覇市'],
])
検証
色々な操作をしていきます。
都道府県の数を求める
オブジェクトがちょっと長い。他2つはどっこい。
arrJ.length
Object.keys(objJ).length
mapJ.size
// 47
県の数を求める
配列がいい感じ。オブジェクトとMapはちょっとごちゃごちゃ。
arrJ.filter(([p]) => p.includes('県')).length
Object.keys(objJ).filter(p => p.includes('県')).length
[ ...mapJ].filter(([p]) => p.includes('県')).length
// 43
都道府県一覧を求める
3つともシンプル。
arrJ.map(([p]) => p)
Object.keys(objJ)
myMap.keys()
// ['北海道', '青森県', ...
県一覧を求める
配列がつよい。
arrJ.filter(([p]) => p.includes('県'))
Object.keys(objJ).filter(p => p.includes('県'))
[ ...mapJ].filter(([p]) => p.includes('県'))
// ['青森県', '秋田県', ...
県庁所在地一覧を求める
3つともいい感じ。配列はちょっと癖ある。
arrJ.map(([, c]) => c)
Object.values(objJ)
myMap.values()
// ['札幌市', '青森市', ...
検証(上級編)
ここからちょっとむずいやつ。
県の県庁所在地一覧を求める
配列が綺麗。全体的にだいぶ長くなってきた。.filter()より後は3つとも一緒です。
arrJ
.filter(([p]) => p.includes('県'))
.map(([,c]) => c)
Object.entries(objJ)
.filter(([p]) => p.includes('県'))
.map(([,c]) => c)
[ ...mapJ]
.filter(([p]) => p.includes('県'))
.map(([,c]) => c)
// ['青森市', '秋田市', ...
今回のようにmapとfilterを両方使っている場合はreduceでまとめることもできます。.filter()以降をreduceに変えるとこうなる。
.reduce((pre, cur: [p, c]) =>
[
... pre,
... p.includes('県') ? [c] : []
]
,[])
愛知県の県庁所在地を求める
配列がちょっと長い。配列初敗北。オブジェクトが一番短いのはこのパターンだけ。
arrJ.find(([p]) => p === '愛知県')[1]
objJ['愛知県']
mapJ.get('愛知県')
// '名古屋市'
名古屋市が県庁所在地の県を求める
逆バージョンは配列が綺麗。
arrJ.find(([,c]) => c === '名古屋市')[0]
Object.entries(objJ).find(([,c]) => c === '名古屋市')[0]
[ ...mapJ].find(([,c]) => c === '名古屋市')[0]
// '愛知県'
都道府県名と県庁所在地名が一緒の場所一覧を求める
青森県青森市みたいなパターンな。また配列の勝ち。これも.filter()以降は一緒。
arrJ
.filter(([p, c]) => p.slice(0, -1) === c.slice(0, -1))
.map(([p]) => p.slice(0, -1))
Object.entries(objJ)
.filter(([p, c]) => p.slice(0, -1) === c.slice(0, -1))
.map(([p]) => p.slice(0, -1))
[ ...mapJ]
.filter(([p, c]) => p.slice(0, -1) === c.slice(0, -1))
.map(([p]) => p.slice(0, -1))
// ['青森', '秋田', ...
これもreduceで代替できますがさっき書いたのでパス。代わりにmap→filterの順番で処理する書き方を見てみよう。今までのやつはfilter→mapの順番だからその逆パターンね。
arrJ
.map(([p, c]) => (p.slice(0, -1) === c.slice(0, -1) ? p.slice(0, -1) : null))
.filter(Boolean)
filterが後に来るパターンはfilterの中がシンプルになるのが長所。でもちょっと処理が重くなるので注意ね。まあそんな変わらんけど。
結論
個人的には配列が有利な場面が多いんじゃないかな?といった印象でした。特に戻り値が複数になるような場合に配列がかなり強いと思います。
オブジェクトやMapは.map()とか.filter()の関数型メソッドに直接繋げられないのがあまりにも不便でした。ただ、ループをfor-ofで行うようなオブジェクト指向的な書き方をするならMapオブジェクトの方が便利かもしれません。その辺りは私が詳しくないので、今回は一旦これで検証終了しようと思います。
最後にもう1回。オブジェクトはmapとかfilterに直接繋げられないのマジ勘弁。以上。