順番に読み進めながら学べます

Map と Set — 重複排除とキー付き集計

同じ値を 1 つしか持たない Set と、キーと値の組を順番どおりに持つ Map を扱います。重複の取り除き方、追加と削除、キーで値を引く書き方、Object との使い分けまで進みます。

商品に付いたタグを配列で集めると同じタグが何度も混ざり、実際に何種類あるかを知るには includes で毎回確かめる手間が要ります。商品 ID ごとの在庫数のようにキーと値の組を持ちたいときも、配列だけでは ID と数量を 2 本の配列に分けて対応づける必要があり、オブジェクトのキーは文字列と symbol に限られるため数値をそのままキーにはできません。

この記事では、重複を持たない Set と、文字列以外もキーにできる Map を扱います。

重複を取り除く — Set の作り方

タグ付きの商品一覧から、実際に使われているタグが何種類あるかを知りたいとします。配列のままでは同じタグが複数回並ぶため、そのまま数えると件数が水増しされます。重複をあらかじめ取り除いて持てるデータ構造が必要です。

Set(同じ値を 1 つしか持たないデータ構造)は new Set(tags) のように配列を渡して作ります。new を先頭に付けると、Set や Map のような決まった種類のデータ構造を新しく 1 つ作るという意味になります。

同じ値が来ても増えないので、size を読むだけで種類の数が分かります。同じ値かどうかは === と同じ基準で判定されます。

Set は配列ではないため、uniqueTags[0] のように index を指定しても読み出せません。配列として扱いたいときは [...uniqueTags] のようにスプレッド構文で変換します。

並び順は追加された順のまま保たれるので、元の配列に現れた順序がそのまま残ります。

Set は同じ値を 1 つにまとめる
1 件目セールまだ持っていない1 件目として入る2 件目新着まだ持っていない2 件目として入る3 件目セールすでに持っている件数は増えない
すでに持っている値が来たときは何も起きません。追加された順序はそのまま残ります。
const tags = ["セール", "新着", "セール", "送料無料"];

// 配列を渡すと、重複を取り除いた Set ができる
const uniqueTags = new Set(tags);
console.log(uniqueTags.size);              // 3

// スプレッド構文で配列に戻す(追加された順のまま)
console.log([...uniqueTags].join(", "));   // セール, 新着, 送料無料

// 元の配列はそのまま
console.log(tags.length);                  // 4

// 空の Set から作り始めることもできる
const empty = new Set();
console.log(empty.size);                   // 0

商品に付いたタグの配列から、実際に使われているタグの種類を取り出します。tags は宣言済みです。

① 元の配列の件数を表示してください。

② 重複を取り除いたデータ構造を作り、その件数を表示してください。

③ 重複を取り除いた結果を配列に戻して、「, 」でつないで表示してください。

④ ③ の配列の 2 番目を表示して、並び順が保たれていることを確かめてください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

値を追加・削除する — add と has と delete

訪問したユーザー ID を記録しておき、同じ人が何度来ても 1 人として数えたい場面があります。配列で持つと、追加のたびに includes で調べてから push する必要があり、記録件数が多いほど確認に時間がかかります。

Set には値を足す add、持っているかを調べる has、取り除く delete、件数を読む size があります。add は同じ値が来ても何も起こらないので、追加前に確認する必要がありません。

has は true か false を返し、delete は取り除けたかどうかを返します。

add と delete で size がどう動くか
add で新しい IDまだ持っていないsize が1 増えるadd で同じ IDすでに持っているsize は変わらないdelete でID を指定記録から取り除くsize が1 減る
同じ値を何度 add しても件数は増えません。取り除くときだけ件数が減ります。
const visitors = new Set(["u-102", "u-337"]);

// 持っているかを調べる
console.log(visitors.has("u-102"));   // true
console.log(visitors.has("u-999"));   // false

// 足す。同じ値なら件数は変わらない
visitors.add("u-541");
console.log(visitors.size);           // 3
visitors.add("u-541");
console.log(visitors.size);           // 3

// 取り除く。取り除けたかどうかが返る
console.log(visitors.delete("u-102"));  // true
console.log(visitors.delete("u-102"));  // false(もう無い)
console.log(visitors.size);             // 2

その日の訪問ユーザーの記録を操作して、人数の変化を確かめます。visitors は宣言済みです。

① u-337 が記録済みかどうかを表示してください。

② u-999 を記録して、件数を表示してください。

③ 同じ u-999 をもう一度記録して、件数を表示してください。

④ u-102 を記録から取り除いて、まだ残っているかどうかを表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

キーと値の組を持つ — Map

商品 ID ごとの在庫数のように、キーと値を組にして持ちたい場面があります。オブジェクトでも実現できますが、ID のように実行時に決まるキーを集計したり、あとから増減させたりする用途には、専用のメソッドを持つデータ構造のほうが向いています。

Map(キーと値の組を登録した順に持つデータ構造)は new Map() で作り、set(キー, 値) で登録します。読むときは get(キー)、あるかどうかは has(キー)、件数は size です。

同じキーで set をやり直すと値が上書きされます。最初から中身を入れるときは new Map([["P-001", 12], ["P-002", 5]]) のように、組の配列を渡します。

Map はキーで組を出し入れする
set でP-001 と 12組として登録するsize が1 増えるget でP-001 を指定キーを探す値の 12が返るget でP-999 を指定見つからないundefinedが返る
set で組を登録し、get でキーから値を引きます。登録が無いキーは undefined が返ります。
const stock = new Map();

// set で組を登録する
stock.set("P-001", 12);
stock.set("P-002", 5);
console.log(stock.size);            // 2

// get でキーから値を引く
console.log(stock.get("P-002"));    // 5
console.log(stock.get("P-999"));    // undefined
console.log(stock.has("P-999"));    // false

// 同じキーで set し直すと上書きされる
stock.set("P-001", 8);
console.log(stock.get("P-001"));    // 8
console.log(stock.size);            // 2(件数は増えない)

// 最初から組を入れて作ることもできる
const prices = new Map([["P-001", 3980], ["P-002", 2480]]);
console.log(prices.get("P-001"));   // 3980

Map の値はドット記法では読めない

stock.get("P-001") のかわりに stock["P-001"] と書いても値は返りません。この書き方は Map 自身のプロパティを探しにいくため、登録した組とは無関係で undefined が返ります。読み書きは get と set を通します。

商品 ID をキーにして在庫数を持つデータ構造を組み立てます。stock は空の状態で宣言済みです。

① P-001 を 12、P-002 を 5、P-003 を 3 として登録し、件数を表示してください。

② P-002 の在庫数を表示してください。

③ 登録していない P-999 の在庫数を表示してください。

④ P-001 の在庫を 4 に更新して、更新後の値を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

Object との使い分け — キーの型と並び順

キーと値を持つという点ではオブジェクトと同じなので、どちらで書くかを選びます。設定項目のようにキーがコードを書く時点で決まっているならオブジェクト、ID や日付のように実行時に増えていくキーを集計するなら Map が扱いやすくなります。

判断の材料になるのが、キーの型と並び順です。

オブジェクトのキーは文字列に変換されるので、record[2026] と書いても実際のキーは "2026" です。Map は渡した値をそのままキーにするため、数値の 2026 と文字列の "2026" は別のキーとして扱われます。

並び順も、Map は登録した順をそのまま保ちます。

Map はキーの型をそのまま保つ
Map に数値 2026 で登録数値のままキーになるget(2026)で取れる同じ Map を文字列で引く別のキー扱いになるundefinedが返るObject に数値 2026 で登録文字列に変換される文字列でも取れてしまう
Map は数値の 2026 と文字列の "2026" を 別のキーとして区別 します。オブジェクトは文字列に変換します。
観点ObjectMap
キーに使える型文字列に変換されるどの型でもそのまま
並び順数字のキーが先に並ぶ登録した順のまま
件数の数え方Object.keys の lengthsize
値の読み書きobj.key / obj[key]get と set
向いている場面決まった項目の集まり実行時に増えるキーの集計

Map の中身を一覧するときは [...stock.entries()] と書いて、[キー, 値] の 2 要素の配列が並んだ配列に直します。こうすると entries[0] のように 1 組ずつ取り出せます。組の数ぶん同じ処理を繰り返す書き方は、繰り返しを学ぶ記事であらためて扱います。

キーだけが必要なときは stock.keys() を使いますが、これが返すのは配列ではないので join を直接つなげられません。[...stock.keys()] と書いて配列にしてから使います。

const scores = new Map([["math", 80]]);

// Map は数値のキーを数値のまま持つ
scores.set(2026, "今年の集計");
console.log(scores.get(2026));               // 今年の集計
console.log(scores.get("2026"));             // undefined(別のキー)

// オブジェクトはキーが文字列に変換される
const record = {};
record[2026] = "今年の集計";
console.log(Object.keys(record)[0]);         // 2026
console.log(typeof Object.keys(record)[0]);  // string
console.log(record["2026"]);                 // 今年の集計(同じキー扱い)

// keys() / entries() はスプレッド構文で配列にしてから使う
console.log([...scores.keys()].join(", "));   // math, 2026

const pairs = [...scores.entries()];
console.log(pairs[0].join(": "));             // math: 80

// オブジェクトは整数のようなキーが先に並ぶ
const ranking = {};
ranking[3] = "c";
ranking[1] = "a";
console.log(Object.keys(ranking).join(", "));  // 1, 3

在庫の Map を一覧表示してから、キーの型による違いを確かめます。stock は 3 組を登録した状態で宣言済みです。

① 登録されているキーだけを配列にして、「, 」でつないで表示してください。

② 3 組を [キー, 値] の配列に直し、1 組ずつ「キー: 値」の形にして 3 行で表示してください。

③ 数値の 2026 をキーにして「年間ランキング」を登録し、そのキーで取り出して表示してください。

④ 同じ値を文字列の "2026" で取り出して、返ってきた値を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1const ids = new Set(["u-1", "u-2"]); のあとに ids.add("u-1"); を実行すると、ids.size はいくつですか?

Q2stock が Map のとき、登録していないキーを stock.get("P-999") で引くと何が返りますか?

Q3数値の 2026 をキーとして登録したとき、Map とオブジェクトで違うのはどこですか?