~文法メモ~ Map と Set

Map

「連想配列」「map」あるいは、「Dictionary」と言われるもの

コンパイル系言語、スクリプト系言語を問わず、ほとんどの言語で存在する。

従来のJavaScriptえは、Objectで対応してきたが、Mapを利用することで

  • Object のキーは「文字列」と「Symbol」ですが、Map では「任意の型」をキーにすることが出来る
  • Mapはsize(要素数)を簡単に取得することが出来る。一方、Objectでは、forなどを利用して手動で取得する必要があった。
  • Mapだとゴミが無い。Objectはプロトタイプを持つため、既定のキーが最初から存在した。
    (但し、これは、Objectでも、 「map = Object.Create(null)」として回避することは可能)

勘違いしてはならないのは、「いつでもMapを使った方がよい」というわけではないということ

  • 文字列でないキーが必要か?
  • キーと値のペアを時々、追加したり削除したりするか?
  • キーが実行時までわからなかったり、キーを直接調べる必要があるか?
  • 対象のMapコレクションをイテレートするか?
  • 全てが同じ型か?

こういった場合は、既存のObjectではなく、Mapを使ったほうがよいかもしれないサインとなります。

ObjectでもMapでもさして変わらないが、最も簡単な例

let m = new Map();
m.set("foo", "ふ~");
m.set("baa", "ば~");
m.set("ham", "はむ");
m.set("egg", "えっぐ");
hm.debuginfo(m.get("egg")); // えっぐ

文字列以外をキーに出来る

参照系(function型やobject型)では、あくまでも一種の「メモリアドレス的な、レジストリーindex的」なものを キーとします。よって表記が同じかどうかではなく、対象のレジストリーindexが同じかどうかが重要です。

このあたりも他のプログラミング言語の連想配列と同様です。

var myMap = new Map();

var keyString = "文字列";
var keyObj = {},
var keyFunc = function () {};

// 値を設定する
myMap.set(keyString, "'文字列' と関連付けられた値");
myMap.set(keyObj, "keyObj と関連付けられた値");
myMap.set(keyFunc, "keyFunc と関連付けられた値");

hm.debuginfo( myMap.size ); // 3

// 値を取得する
hm.debuginfo( myMap.get(keyString) );    // "'文字列' と関連付けられた値"
hm.debuginfo( myMap.get(keyObj) );       // "keyObj と関連付けられた値"
hm.debuginfo( myMap.get(keyFunc) );      // "keyFunc と関連付けられた値"

hm.debuginfo( myMap.get("文字列") );     // "'文字列' と関連付けられた値"
                         // keyString === '文字列' であるため
hm.debuginfo( myMap.get({}) );            // undefined, keyObj !== {} であるため
hm.debuginfo( myMap.get(function() {}) ); // undefined, keyFunc !== function () {}

for...ofを利用した反復

.keys(), .values(), .entries()などが用意されています。
デフォルトでは.entries()と同様の動作となります。

let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (let [key, value] of myMap) {
  hm.debuginfo(key + " = " + value);
}
// 0 = zero
// 1 = one

for (let key of myMap.keys()) {
  hm.debuginfo(key);
}
// 0
// 1

for (let value of myMap.values()) {
  hm.debuginfo(value);
}
// zero
// one

for (let [key, value] of myMap.entries()) {
  hm.debuginfo(key + " = " + value);
}
// 0 = zero
// 1 = one

// Mapを順々に処理
myMap.forEach( (value, key) => hm.debuginfo(`${key}=${value}`) );

myMap.set("test", 55);
// 特定のキーを削除
myMap.delete("test");

// 全てのキーと値のペアを削除
myMap.clear();

forEach

.keys(), .values(), .entries()などが用意されています。
デフォルトでは.entries()と同様の動作となります。s

Set

一意な値の「集合」の概念です

コンパイル系言語、スクリプト系言語を問わず、似たようなことが出来る言語も数多くあります。
Mapと類似したものであり、実際しばしばMapの代替としてSetが利用されます。

Setの最も簡単な例

var mySet = new Set();

mySet.add(1);
mySet.add(5);
mySet.add("some text");
var o = {a: 1, b: 2};
mySet.add(o);

mySet.has(1); // true
mySet.has(3); // 3 は集合にないため、false
mySet.has(5);              // true
mySet.has(Math.sqrt(25));  // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true

mySet.size; // 4

mySet.delete(5); // 集合から 5 を削除
mySet.has(5);    // 5 が削除されているため false

mySet.size; // 要素を 1 つ削除しているため 3

for...ofを利用した反復

.keys(), .values(), など、mapと同じ関数が用意されています。
異なる点は、.keys()も.values()も何もメソッドを利用しない状態でも、
全て結果は同じであるということです。

var mySet = new Set();

mySet.add(1);
mySet.add(5);
mySet.add("some text");
mySet.add(() => 3);


// 以下の4つは全部同じとなる。

for (let item of mySet) {
    hm.debuginfo(item);
}

for (let item of mySet.keys()) {
    hm.debuginfo(item);
}

for (let item of mySet.values()) {
    hm.debuginfo(item);
}

// 全部クリア
mySet.clear();

Setの初期化

let mySet = new Set( [1, 5, "some text", () => 3, 100 ] );

というように最初に一気に初期化することが出来ます。