連想配列(Map)のメンバとよく使う操作【JavaScript】


# はじめに

連想配列は、ObjectではなくMapを使ったほうが良い(こともある)という記事を読み、積極的に使うことにしました。

参考:

- [JavaScriptで連想配列を利用する際にObjectではなくMapを使うメリット](https://genzouw.com/entry/2019/07/04/120033/1648/)
- [JavaScriptでObjectじゃなくMapを使う場面の一例](https://qiita.com/ikngtty/items/d9e1e8b1018ec766767c)
- [連想配列はMapを使うべきは本当か？](https://qiita.com/raccy/items/816a322fb330193e788b)


Mapを使うにあたり、「こうしたいときどうするんだっけ？」となった時に参考にできるものが欲しかったので、自分で記事にまとめることにしました。

## この記事について

<font color="SteelBlue">**以下について書いています**</font>

- Mapのメンバ
- Mapでよく使う操作

<font color="MediumVioletRed">**以下については書いていません**</font>

- そもそも配列とは何か
- Object配列との違い

# 連想配列とは何か

連想配列とは・・・
>添え字にスカラー数値以外のデータ型(文字列型等)も使用できる配列である。

連想配列は、配列の値(`value`)に名前(`key`)をつけて管理することができます。

```js
// Arrayだと名前はつけられない
const arrayTestResults = new Array(
  77,
  91,
  82
)

// Mapだと名前をつけられる
const mapTestResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);
```

参考:

- [wiki -連想配列-](https://ja.wikipedia.org/wiki/%E9%80%A3%E6%83%B3%E9%85%8D%E5%88%97)
- [「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 -連想配列-](https://wa3.i-3-i.info/word11931.html)


# 配列の宣言

## 宣言だけする

```js
const map = new Map()
```

## 宣言と同時に初期値を設定する

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);
```

# メンバ

## 一覧

| 名称 | 説明 |
| :---: | :--- |
| [.size](#size) | 要素数を取得する |
| [.has(key)](#haskey) | keyが一致する要素の有無を真偽値で取得する|
| [.get(key)](#getkey) | keyが一致する要素の値を取得する|
| [.set(key,value)](#setkeyvalue)| keyが一致する要素の値を上書き(または追加)する|
| [.delete(key)](#deletekey) | keyが一致する要素を削除する |
| [.clear()](#clear) | 全ての要素を削除する |
| [.keys()](#keys)| 全ての要素のkeyを取得する |
| [.values()](#values) | 全ての要素のvalueを取得する|
| [.entries()](#entries) | 全ての要素のkeyとvalueを取得する |
| [.forEach(callback\[, thisArg\])](#foreachcallback-thisarg)| 引数のcallbackに渡した処理をそれぞれの要素に実行する |

## .size
要素数を取得します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
  ['理科', 55],
  ['地理', 60],
  ['歴史', 49],
]);

const size = testResults.size
// 6
```

*プロパティなのでカッコ`()`が不要です。
`size()`ではなく`size`と書きます。


## .has(key)
keyが一致する要素の有無を真偽値で取得します

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const hasEnglishResult = testResults.has('英語');
// true

const hasHistoryResult = testResults.has('理科');
// false
```

## .get(key)
keyが一致する要素の値を取得します。
keyが一致する要素がなかった場合は`undifined`を取得します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const englishResult = testResults.get('英語');
// 82

const historyResult = testResults.get('理科');
// undefined
```

## .set(key,value)

keyが一致する要素の値を上書きします。
keyが一致する要素が存在しない場合、新たに要素を追加します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

// 上書き
testResults.set('英語', 100);
// Map {
//   '国語' => 77,
//   '数学' => 91,
//   '英語' => 100,
// }

// 追加
testResults.set('理科', 55);
// Map {
//   '国語' => 77,
//   '数学' => 91,
//   '英語' => 100,
//   '理科' => 55,
// }
```

## .delete(key)
keyが一致する要素を削除します。
削除ができたかどうかを有無を真偽値で取得することもできます。

```js

const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

console.log(testResults.delete('数学'));
// true
// Map {
//   '国語' => 77,
//   '英語' => 82,
// }

console.log(testResults.delete('理科'));
// false
// Map {
//   '国語' => 77,
//   '英語' => 82,
// }
```

## .clear()
全ての要素を削除します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
  ['理科', 55],
  ['地理', 60],
  ['歴史', 49],
]);

console.log(testResults.clear());
// Map {}
```


## .keys()
全ての要素のkeyを取得します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const keys = testResults.keys();
// [Map Iterator] {
//   '国語',
//   '数学',
//   '英語'
// }
```

*実行結果はシンボルという特殊な型で出力されます。
配列として扱う方法は次章「よく使う操作」で説明します。


## .values()

全ての要素のvalueを取得します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const values = testResults.values();
// [Map Iterator] {
//   77,
//   91,
//   82
// }
```

*実行結果はシンボルという特殊な型で出力されます。
配列として扱う方法は次章「よく使う操作」で説明します。。

## .entries()
全ての要素のkeyとvalueを取得します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const entries = testResults.entries();
// [Map Entries] {
//   ['国語', 77],
//   ['数学', 91],
//   ['英語', 82]
// }
```

*実行結果はシンボルという特殊な型で出力されます。
配列として扱う方法は次章「よく使う操作」で説明します。


## .forEach(callback[, thisArg])

引数の`callback`に渡した処理を要素それぞれの要素に実行します。
Array.prototype.forEach()と同じような動きをします。

```js
// forEach()
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

testResults.forEach((value, key, map) => {
  console.log(value, key, map);
});
// 77 国語 Map { '国語' => 77, '数学' => 91, '英語' => 82 }
// 91 数学 Map { '国語' => 77, '数学' => 91, '英語' => 82 }
// 82 英語 Map { '国語' => 77, '数学' => 91, '英語' => 82 }
```

# よく使う操作

## Iteratorを配列として扱う

keys()やvalues()の実行結果はシンボルという特殊な型で出力されます。
配列ではないので、配列用の関数を実行するとエラーになります。


```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const keys = testResults.keys();
// [Map Iterator] {
//   '国語',
//   '数学',
//   '英語',
// }

keys.forEach((key) => console.log(key));
// TypeError: keys.forEach is not a function
```

参考:

- [JavaScript互換性の要！Symbol（シンボル）について](https://analogic.jp/symbol/)
- [ECMAScript6にシンボルができた理由](https://qiita.com/naruto/items/312adeb6145eb6221be7)

### 方法1. `Array.from()`
配列として値を取得したい場合は、`Array.from()`を使って変換すればOKです。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const keys = Array.from(testResults.keys());
// [
//   '国語',
//   '数学',
//   '英語'
// ];

keys.forEach((key) => console.log(key));
// 国語
// 数学
// 英語
```


### 方法2. 分割代入(`[...配列]`)
分割代入`[...Map.keys()]`を利用して配列にすることも可能です。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
]);

const keys = [...testResults.keys()];
// [
//   '国語',
//   '数学',
//   '英語'
// ];

keys.forEach((key) => console.log(key));
// 国語;
// 数学;
// 英語;
```

## キーでソートする

数値のkeyを数の若い順に並び替えます

```js
const testResults = new Map([
  [6, '歴史'],
  [1, '国語'],
  [4, '理科'],
  [5, '地理'],
  [2, '数学'],
  [3, '英語'],
]);

const sortedTestResults = new Map(
  [...testResults.entries()].sort((a, b) => a[0] - b[0])
);
// Map {
//   1 => '国語',
//   2 => '数学',
//   3 => '英語',
//   4 => '理科',
//   5 => '地理',
//   6 => '歴史'
// }

```
参考:
[Stack Overflow -Is it possible to sort a ES6 map object?-](https://stackoverflow.com/questions/31158902/is-it-possible-to-sort-a-es6-map-object/31159284)

## 最大値を取得する

値の中で最大の数値を取得します。

```js
const testResults = new Map([
  ['国語', 77],
  ['数学', 91],
  ['英語', 82],
  ['理科', 55],
  ['地理', 60],
  ['歴史', 49],
]);

const maxNum = Math.max(...Array.from(testResults.values()));
// 91
```

## 重複する値の数をカウントする

配列内の同じ値の出現回数を要素ごとにカウントします。

```js
const animals = ['いぬ', 'ねこ', 'いぬ', 'ねこ', 'いぬ', 'いぬ'];
const animalCounts = new Map();

for (let animal of animals) {

  // 一致するkeyがあればカウントアップ
  if (animalCounts.has(animal)) {
    const count = animalCounts.get(animal);
    animalCounts.set(animal, count + 1);

    // 一致するkeyがなければ新たに値をset
  } else {
    animalCounts.set(animal, 1);
  }
}

console.log(animalCounts);
// Map {
//   'いぬ' => 4,
//   'ねこ' => 2,
// }

```


## まとめ
メンバは直感的にわかるものばかりなので覚えやすいのですが、配列への変換操作にはまだ慣れません。。
ネットで調べようとしても、Array.prototype.map()とかObjectの連想配列が混ざっててなかなか目的のものが手に入らないんですよね。
ググラビリティの問題かもですが、、

覚えるまでは、この記事をみながら使おうと思います。


## 参考

- [MDN -Map-](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map)
- [改訂新版JavaScript本格入門(書籍)](https://www.amazon.co.jp/%E6%94%B9%E8%A8%82%E6%96%B0%E7%89%88JavaScript%E6%9C%AC%E6%A0%BC%E5%85%A5%E9%96%80-%EF%BD%9E%E3%83%A2%E3%83%80%E3%83%B3%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%AB%E3%82%88%E3%82%8B%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E7%8F%BE%E5%A0%B4%E3%81%A7%E3%81%AE%E5%BF%9C%E7%94%A8%E3%81%BE%E3%81%A7-%E5%B1%B1%E7%94%B0-%E7%A5%A5%E5%AF%9B-ebook/dp/B01LYO6C1N/ref=sr_1_1?adgrpid=66487072010&dchild=1&gclid=Cj0KCQjwpZT5BRCdARIsAGEX0zn4EDbg2M8_0vMA8-9ewohxctOW6d1AoeCOKiI2YAVkEDEeRQ2ZZI0aAtbPEALw_wcB&hvadid=353008294966&hvdev=c&hvlocphy=1009309&hvnetw=g&hvqmt=e&hvrand=276630196531197784&hvtargid=kwd-978593581432&hydadcr=27268_11561170&jp-ad-ap=0&keywords=%E6%9C%AC%E6%A0%BC%E5%85%A5%E9%96%80+javascript&qid=1596271144&sr=8-1&tag=googhydr-22)