※スマホ察応はしおたせん。

タグ: Map

配列で重耇する項目を取り陀くや぀4皮。配列ずかおれおれAdvent Calendar2018 – 18日目

カテゎリヌ: JavaScript

LINDORのAdvent Calendar本物の18日目を開けたずころ。
配列ずかおれおれAdvent Calendar2018 – 18日目

ありそうでないメ゜ッド、それが重耇排陀です。

Underscore.jsやLodashには uniq() おのがあっお、こんな感じ↓で䜿えたす。

const arr0 = [11, 22, 11, 22, 33];
const arr1 = _.uniq(arr0);

JavaScriptにはありたせん。

Set を䜿う

単玔な倀ならこれが䞀番簡単。

Set は重耇しない倀を栌玍できるオブゞェクトです。 ... で展開しお配列オブゞェクトぞ再倉換したす。

const arr0 = [11, 22, 11, 22, 33];
const arr1 = [...new Set(arr0)];
console.log(arr1); // => [ 11, 22, 33 ]

includes() で確認する

これが䞀番玠盎。

includes() は配列が指定の芁玠を含むかどうか調べるや぀です。 重耇しおなかったら远加するだけ。

const arr0 = [11, 22, 11, 22, 33];

const arr1 = arr0.reduce((a, v) => {
  if (!a.includes(v)) {
    a.push(v);
  }
  return a;
}, []);

console.log(arr1); // => [ 11, 22, 33 ]

reduce() に自信がなければ forEach() でも。

const arr1 = [];
arr0.forEach((v) => {
  if (!arr1.includes(v)) {
    arr1.push(v);
  }
});

some() でやる

Set も includes() もオブゞェクトの堎合は完党に同じむンスタンスでないず反応しないので、その堎合は some() がよろしいかず。

もっず自由床が高い。

const arr0 = [
  { id: '11', num: 1 },
  { id: '22', num: 2 },
  { id: '11', num: 3 },
  { id: '22', num: 4 },
  { id: '33', num: 5 },
];

const arr1 = arr0.reduce((a, v) => {
  if (!a.some((e) => e.id === v.id)) {
    a.push(v);
  }
  return a;
}, []);

console.log(arr1);
// [ { id: '11', num: 1 },
//   { id: '22', num: 2 },
//   { id: '33', num: 5 } ]

重耇分は先に出おくるや぀が優先です。

Map を䜿う

これもオブゞェクトに察応。初芋で「ぎょえヌ」おなりそう。

const arr0 = [
  { id: '11', num: 1 },
  { id: '22', num: 2 },
  { id: '11', num: 3 },
  { id: '22', num: 4 },
  { id: '33', num: 5 },
];

const arr1 = [...new Map(arr0.map((v) => [v.id, v])).values()];

console.log(arr1);
// [ { id: '11', num: 3 },
//   { id: '22', num: 4 },
//   { id: '33', num: 5 } ]

仕組みは Set ず some() の組み合わせです、だいたい。

  1. map() でIDずオブゞェクト本䜓の組み合わせぞ倉換
  2. 組み合わせを元に Map オブゞェクトを䜜成。ここでID Map のキヌが重耇するものは排陀
  3. values() でオブゞェクト Map の倀のみの反埩子を埗る
  4. スプレッド構文 ... を䌎う配列初期化子 [] で配列に。完成

重耇分は埌ろにあるや぀が優先䞊曞きです。

おしたい

なんか良いのある

参考

WeakMap、WeakSetで「匱い参照」を䜿えるようになったぞ。珟代的JavaScriptおれおれアドベントカレンダヌ2017 – 21日目

カテゎリヌ: JavaScript

珟代的JavaScriptおれおれアドベントカレンダヌ2017 – 21日目

抂芁

基本的な䜿い方は Map や Set ず䞀緒です。キヌに䜿われるオブゞェクトの参照カりントを増やさないずかそういうアレで、「匱い参照」で保持したす。メモリリヌク察策ずかに。

Map 、 Set ずの違い

基本は Map 、 Set ず同様なんだけど、キヌが匱い参照埌述になりたす。

できるこず:

  • get() (WeakMap)
  • set() (WeakMap)
  • add() (WeakSet)
  • has()
  • delete()

できないこず:

  • size
  • forEach()
  • clear()
  • keys()
  • 他

キヌはオブゞェクトのみ

Map は䜕でもキヌに䜿えたんだけど、 WeakMap はオブゞェクトだけです。

たあ意味ないしね。

匱い参照

JavaScript界隈では新しい抂念のはず。

プログラム内で䜿っおる情報っおメモリに眮かれるじゃないですか。あれっお䜿われおる間は保持されおお、䜿われなくなったら消しおくれるんですよ、誰かが。䞊䜍のプログラムが。

で、問題はどうやっお「䜿われおいるか」を刀断するかなんだけど、倉数から参照されおる数を芚えおおく、ずいうのがよくある基本的な仕組みです。そんでその数を「参照カりンタ」ずか呌んだりしたす。参照カりンタが0になったらもう誰も芋おないから消しちゃおう、おいうね。

参照を数える様子。

倉数はスコヌプが終わるず消えお、参照カりンタが枛っお、他から参照されおなければ参照先の情報も消える、ず。

匱い参照だず、その参照カりンタを増やさずにおくこずができたす。なので、匱い参照が残っおいおもメモリ䞊の情報は消えおしたうこずもありたす。

WeakMapのキヌは匱い参照

WeakMapのキヌずしおのみ䜿われおいる堎合、そのリ゜ヌスは削陀されたす。

WeakMap objects are collections of key/value pairs where the keys are objects and values may be arbitrary ECMAScript language values. A WeakMap may be queried to see if it contains a key/value pair with a specific key, but no mechanism is provided for enumerating the objects it holds as keys. If an object that is being used as the key of a WeakMap key/value pair is only reachable by following a chain of references that start within that WeakMap, then that key/value pair is inaccessible and is automatically removed from the WeakMap. WeakMap implementations must detect and remove such key/value pairs and any associated resources.

WeakMapオブゞェクトはkey/valueペアのコレクションです。keyはオブゞェクト、valueはECMAScript蚀語の任意の倀を取りたす。WeakMapはあるkey/valueペアを栌玍しおいるかを、特定のkeyを甚いお確認できるよう芁求されるかもしれたせんが、キヌずしお保持しおいるオブゞェクトの列挙に぀いおの手順は提䟛されおいたせん。もしWeakMapのkey/valueペアのキヌずしお䜿われおいるオブゞェクトが、そのWeakMap内から開始する参照チェむンを蟿っおのみ到達できる堎合、そのkey/valueペアはアクセスできず、たた自動的にそのWeakMapから削陀されたす。WeakMapの実装は、このようなkey/valueペア及び関連リ゜ヌスを怜出し、削陀する必芁がありたす。

匷調は匕甚者。

匱参照ずか蚀ったけど、仕様曞ではそういう単語は䜿っおないし、実装方法が任意なので参照カりンタが甚いられおいるずも限らないです。たぶん䜿っおるず思うんだけど。

倀の方は普通の参照です。

継承しお

仕様曞によるず「サブクラスずしお䜿えるように蚭蚈されおいる」ずのこずです。

内郚の初期凊理が必芁なので、コンストラクタで super 呌び出ししないず怒られたす。

class ElementData extends WeakMap {
  constructor() {
    // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from deriv
  }
}

HTML芁玠をキヌに

DOMノヌドだったら同じ内容で別むンスタンスみたいなものっおあんたり䜜られない感じがするので、キヌにするのに良さそう。jQueryの data() みたいなや぀。

const elementData = new WeakMap();

const el = document.querySelector('#foo');
elementData(el, { text: '远加情報 ' });

console.log(elementData.get(el));

Reactずかでがんがん芁玠再生成したすヌみたいな蚭蚈の堎合は、たぶん識別子ずか持っおるんじゃないかな。そんなら普通の Map で十分そう。いや自動削陀ないか。

実際の利甚ケヌス

Stack Overflowの解答から。

Some use cases that would otherwise cause a memory leak and are enabled by WeakMaps include:

  • Keeping private data about a specific object and only giving access to it to people with a reference to the Map. A more ad-hoc approach is coming with the private-symbols proposal but that’s a long time from now.
  • Keeping data about library objects without changing them or incurring overhead.
  • Keeping data about a small set of objects where many objects of the type exists to not incur problems with hidden classes JS engines use for objects of the same type.
  • Keeping data about host objects like DOM nodes in the browser.
  • Adding a capability to an object from the outside (like the event emitter example in the other answer).

WeakMapを䜿わないずメモリリヌクを匕き起こす利甚ケヌス:

  • あるオブゞェクトに関するプラむベヌトな情報を保持し、Mapぞの参照を経由しおのアクセスのみを公開する。より盎接的な手段ずしおプラむベヌトシンボルが提案されおいるが、ただ時間かかりそう
  • ラむブラリオブゞェクトに関する情報を、ラむブラリを倉曎したりオヌバヌヘッドを招かずに保持する
  • 小さなオブゞェクトの組み合わせに関する情報を保持する。この皮の倚数のオブゞェクトは、JS゚ンゞンが同皮のオブゞェクトのために䜿う隠しクラスに関する問題を匕き起こさないために存圚する蚳泚: ごめんむミフ
  • ブラりザのDOMノヌドのような、ホストオブゞェクトに関する情報を保持する
  • あるオブゞェクトの倖偎から胜力 (capability) を远加する他の解答にあるむベント゚ミッタの䟋みたいに

茉せずいおなんだけど正盎䜕蚀っおんのかよくわかんない  。

その他

キヌ䞀芧機胜の提䟛は犁止

速床問題に発展しがちなので、そういうのは実装しちゃだめ (must not) ず仕様曞に曞かれおたす。

あれ、そういう理解で合っおるよね

参考

デヌタ保持ならObjectよりMapの方が良いの珟代的JavaScriptおれおれアドベントカレンダヌ2017 – 19日目

カテゎリヌ: JavaScript

珟代的JavaScriptおれおれアドベントカレンダヌ2017 – 19日目

良いか

抂芁

Array#map() じゃなくお Map ずいうビルトむンコンストラクタです。

蟞曞 (dictionary) みたいなノリで䜿えたす。

const map = new Map();

map.set(100, 'Number 100');
console.log(map.get(100));  // => "Number 100"

for (let [key, value] of map) {
    console.log(`[${key}] = [${value}]`);
}

䜿い方

new で䜜っお名前ず倀の組み合わせの情報を栌玍したす。

情報の远加ず取埗

set() で远加ず䞊曞き、 get() で取埗。

const map = new Map();
const key = 100;

// set
map.set(key, { message: 'Hello World!' });

// get
const value = map.get(key);

concole.log(key, value);

set() は map オブゞェクト自䜓を返すので、メ゜ッドチェむンみたいに曞けたす。

map
  .set(100, 'Hello')
  .set(101, 'World')
  .set(102, '!')

あんたり曞かない方が良いず思うけど。

有無の確認

const map = new Map();

console.log(map.has(1));  // => false

map.set(1, 'one');
console.log(map.has(1));  // => true

削陀

delete() でキヌを指定しお消したす。削陀できた堎合は true が、もずもず倀を持っおいなかった堎合は false が垰りたす。 delete 挔算子ず違うね。

あず clear() で党郚消したす。こちらは垞に undefined を返す。

const map = new Map();
let deleted;

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

console.log('Size:', map.size);  // => Size: 3
console.log('#101', map.get(101));  // => #101 Goodmorning Universe!

// ひず぀削陀
deleted = map.delete(101);
console.log('Size:', map.size);  // => Size: 2
console.log('#101', map.get(101));  // => #101 undefined
console.log('Delete successfully?', deleted);  // => true

// 削陀枈みのものを削陀
deleted = map.delete(101);
console.log('Delete successfully?', deleted);  // => false

// 党郚削陀
map.clear();
console.log('Size:', map.size);  // => Size: 0

キヌは䜕でも

オブゞェクトの堎合は基本的に toString() で文字列に倉換されるんだけど、 Map の堎合は型も含めおそのたたキヌになりたす。 1 ず "1" は別物扱いです。

const map = new Map();

// 数倀の1ず文字列の"1"
map.set(1, 'Number 1');
map.set('1', 'String 1');

console.log(map.get(1));  // => "Number 1"
console.log(map.get('1'));  // => "String 1"

各皮オブゞェクトをそのたた突っ蟌むこずもできたす。

繰り返す

forEach() あるいは for-of ルヌプでぐるぐるできたす。

あず size ずいう、配列の length みたいなプロパティがありたす。

const map = new Map();
let deleted;

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

console.log('Size:', map.size);  // => Size: 3

map.forEach((value, key) => {
    console.log('forEach', key, value);
})

for (let [key, value] of map) {
    console.log('for-of', key, value);
}

forEach() 以倖の、 Array.prototype 系のメ゜ッドはないです。

キヌ、倀をたずめお取埗

keys() でキヌだけ、 values() で倀だけ、さらに entries() でキヌず倀の組のむテレヌタを埗られたす。

const map = new Map();

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

for (let key of map.keys()) {
    console.log('key', key);
}

for (let value of map.values()) {
    console.log('value', value);
}

for (let [key, value] of map.entries()) {
    console.log('key-value', key, value);
}

[] アクセスはだめ

マップじゃなくお普通のむンスタンスプロパティぞのアクセスになりたす。

const map = new Map();

map.set(100, 'Hello World!');
map[101] = 'Goodmorning Universe!';

console.log(map.size);  // => 1
console.log(map.get(100));  // => "Hello World!"
console.log(map.get(101));  // => undefined
console.log(map['101']);  // => "Goodmorning Universe!"

オブゞェクトに远加情報を持たせる

他に独自の情報を持ちたいけど元のオブゞェクトに觊りたくないなヌずいうずき、そのオブゞェクト自䜓をキヌにしお、远加情報を別途眮いおおくこずができたす。

const map = new Map();

const el = document.querySelector('#foo');
const someDataForTheElement = {};
map.set(el, someDataForTheElement);

でも別むンスタンスになるず別物っおいう扱いになっちゃうので、氞続的に䜿える識別子があればそっちの方が良さそう。

䜿い道

ず、ここたで曞いおきたけど正盎これっおいう䜿い道が思い぀かない  。

オブゞェクト Object ないし {} でも同じようなこずはできたす。今たでずっずこっちでやっおきたしたね。

const map = {};
map[100] = 'A hundred';
map[101] = 'A hundred and one';

console.log(100, map[100]);  // => "A hundred"

Object.keys(map)
    .forEach((key) => {
        const value = map[key];
        console.log(key, value);
    });

反埩凊理ずかちょっずアレだけどたあどうにかなるし、ただあんたりよくわかんない。

Object ず Map の違い

  • [] の代わりに set() 、 get() を䜿う
  • キヌに文字列以倖も䜿える 1 ず "1" が別物扱い
  • 反埩凊理 for-of できる
  • size で数を取れる

MDNには

こう茉っおる。

『オブゞェクトずマップの比范』より。

これは Map をい぀も䜿えばいいずいうこずではありたせん。オブゞェクトはただ倚くの堎面で䜿えたす。Map むンスタンスはコレクションずしお䜿う堎合のみに圹に立ちたす。以前オブゞェクトをこのように䜿っおいたコヌドに Map を䜿うこずを考えおみるべきです。オブゞェクトはメンバ倉数ずメ゜ッドを備えたレコヌドずしお䜿われたす。もしただどちらを䜿えばいいかわからないなら、以䞋の質問に答えおみおください。

  • キヌがい぀も実行時たでわからない、たたはキヌを盎接調べる必芁がありたすか?
  • すべおの倀が同じ型で、亀換しお䜿甚できたすか?
  • 文字列でないキヌが必芁ですか?
  • キヌず倀のペアを時々、远加たたは削陀したすか?
  • 簡単に量が倉わるキヌず倀のペアがありたすか?
  • そのコレクションをむテレヌトしたすか?

これらはあなたが Map をコレクションずしお䜿いたいずきのサむンです。もし察照的に、固定された量のキヌがあり、それら個々に操䜜し、Mapの䜿い方ず区別する堎合、オブゞェクトを䜿いたしょう。

その他

NaN が合臎する

JavaScriptの现かい話で良く出おくるや぀なんですが、 NaN “Not a Number” は特殊な倀で、自身ず等倀になりたせん。

const n = NaN;
console.log(n === n);  // false (wow)

が、マップのキヌずしお䜿う堎合、内郚で同じ倀ずみなしおくれたす。

const map = new Map();

map.set(NaN, 'Not a Number');
console.log(map.get(NaN));  // => "Not a Number"
map.set(NaN, '!');
console.log(map.get(NaN));  // => "!"

もちろん NaN ず文字列 "NaN" は合臎したせん。やったね。

オブゞェクト Object のキヌ

䞊の方で「 obj[key] の key は基本的に文字列に」みたいに蚀ったけど、 Symbol おのも䜿えるようになりたした。

参考

  • ECMAScript® 2017 Language Specification
    • 23.1 Map Objects
    • 23.1.3 Properties of the Map Prototype Object … 各皮メ゜ッドずプロパティ
    • 7.2.10 SameValueZero ( x, y ) … get() でキヌの合臎を確認する内郚凊理
    • 12.3.2.1 Runtime Semantics: Evaluation … obj[key] の解釈
    • 7.1.14 ToPropertyKey ( argument ) … obj[key] の key の解釈
  • Map – JavaScript | MDN
  • Symbol – JavaScript | MDN