はじめに
新しい職場で初めてTypeScriptを書くことになり、日々新しい文法と格闘していたら、先輩がマップ処理についての個人講義を開いてくれました!
その一言目が、「map処理の本質は写像」...
先輩のサポートもあり、講義が終わる頃には自分も、「map処理のホンシツハ...シャゾウ...」と言えるくらいには理解が進んだのでその復習も兼ねてのアウトプットになります!
この記事の到達点
- 「map処理の本質は写像」とドヤれる
- map処理の使いどころが分かる
- (オマケ)map処理とMap型の違いが分かる
この記事の対象者
- map処理は複雑そうで苦手意識がある
- Flutter/DartのMapと混じるな...(自分)
開発環境
今回は環境構築不要なオンラインエディターを使いました!(他言語もあり便利)
目次
1. map処理の本質は写像
「map処理の本質は写像」の一番の根拠は以下です。(Wikipedia)
出オチです。
「なんとなく数学用語なんだろうな」という粒度でしか写像という言葉を認識していなかったですが、どうやら英語ではmapと言うそう...
じゃあ納得!とはならないので、もう少し詳しく見ていきましょう。
写像(map)の定義は、
二つの集合が与えられたときに、一方の集合の各元に対し、他方の集合のただひとつの元を指定して結びつける対応のことである。
とされています。
つまり、ある集合の要素と、別の集合のただ一つの要素との一対一の対応関係を表すものということです。(超訳)
一方で、JavaScriptにおける 写像(map)は以下の説明が以下になります。
与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。(MDN)
こちらもやはり、ある集合(配列)の要素に対して別の集合(新しい配列)の要素を対応させる操作です。
具体例を見てみましょう。
例えば、次のコードは配列の各要素を2倍にする写像(map)です。
const a = [1, 2, 3, 4];
const b = numbers.map(x => x * 2);
console.log(b); // 出力: [2, 4, 6, 8]
配列a [1, 2, 3, 4]
に対して、配列b [2, 4, 6, 8]
が一対一で対応しています。
このように、JavaScriptにおいても、ある配列と、一対一対応の別の配列を新たに作る操作を、 map処理 といいます。
2. map処理の使いどころ
map処理を使う場面としては、主に以下の2つの場合があります。
1. 配列の変換
先の配列a
を2倍した配列b
を作る処理が、これに当たります。
map処理を使わないと...
// それぞれの配列を定義
let a = [1, 2, 3, 4, 5];
let b = [];
// for文でaの中身を2倍した値をbに挿入していく
for (let i = 0; i < a.length; i++) {
b.push(a[i] * 2);
}
console.log(b); // [2, 4, 6, 8, 10]
これだけ長い処理(4行)が、
const a = [1, 2, 3, 4];
// map処理(配列の各要素をxとして受け取り、それを2倍した値を返すアロー関数)
const b = numbers.map(x => x * 2);
console.log(b); // 出力: [2, 4, 6, 8]
mapを使えば1行でできるようになります。
2. データの整形
map処理は、データの必要な部分のみを取り出す時にも使えます。
例えば、menu配列
の中から、name(料理名)
だけを取り出した配列を作りたい場合、mapを使わないと...
let menu = [
{ name: 'チャーハン', price: 650 },
{ name: '麻婆豆腐', price: 700 },
{ name: '小籠包', price: 450 }
];
let foods = [];
// for文で一つずつ取り出してfoods配列にpushする
for (let i = 0; i < menu.length; i++) {
foods.push(menu[i].name);
}
console.log(foods); // ['チャーハン', '麻婆豆腐', '小籠包']
と、なりますが、mapを使えば
let menu = [
{ name: 'チャーハン', price: 650 },
{ name: '麻婆豆腐', price: 700 },
{ name: '小籠包', price: 450 }
];
// nameを取り出して
let foods = menu.map(x => x.name);
console.log(foods); // ['チャーハン', '麻婆豆腐', '小籠包']
とシンプルに記述できるようになります。
アロー関数の部分を、分割代入を使ってよりシンプルかつ直感的に書く方法もあります。
※分割代入(Destructuring Assignment)MDN
let foods = menu.map(({ name }) => name); // name要素を取り出す
3. map処理とMap型の違い
結論から言うと、全くの別物です!
しかし、自分は先輩から「map」と聞いた時、恥ずかしい話ですがMap型と混同してしまいました...
Dartなど別言語では「Mapと言えば型」という経験があったので、それに引っ張られた説があります。💦
さて、そんなMap型ですが、以下のようなものになります。(MDN)
const menu = new Map();
menu.set('チャーハン', 650);
menu.set('麻婆豆腐', 700);
menu.set('小籠包', 450);
console.log(menu.get('チャーハン')); // 650
キーの集合とバリューの集合が一対一で対応しているという点で、写像のため、Map型と呼ばれますが、
先ほどまでの配列を操作するmap処理自体とは何ら関係がなく、他人の空似です。(紛らわしい!!)
このようなキー・バリューの対応をさせる場合、JavaScriptだとオブジェクト(MDN)を使うことが大半なので、あまりお目にかかることはありません。
こちらの記事のように、存在確認をしやすいなどのメリットがあるそうなので、Map型を使いこなせるようになれば一流のJavaScriptエンジニア感がありますね...(憧れ)
【JavaScript】オブジェクトより便利かも!? Map を使ってみよう!
おわりに・まとめ
先輩に個別講義をしてもらったことを契機に、これまで避け続けてきたmap処理に向き合うことができました。
その過程で、アロー関数やreduce
, filter
など、必須知識もインプットできたのが一番良かったです。
新技術に触れるときは、概観を掴むのと、何か一つよく使う機能をガッツリ調べると、付随して色々な知識がついてきて良いなぁと、自分なりのインプットのベストプラクティスを見つけられた気がしています。
早くチームにコントリビュートできるように頑張るぞぅ!
参考資料