動機
コレクション(C#でいうHashSetやList)が欲しいことってありますよね。
ES6であれば、JavaScriptにMapクラスが提供されているので独自実装する必要はないのですが、
ES5にはないので、Mapクラスを自作する必要があります。
※友人AのMacのSafariでページの一部(ES6のMapクラス使ってる部分)が動作しないと苦情が。対応せねば。
※友人BのWindows VistaのレガシーなIEでほぼ動かないと苦情が。これは黙殺します。
参考になりそうな記事やサイト
- typescript 独自オブジェクトの配列と連想配列(ハッシュ)の型の書き方
- collections - Is there a typescript List<> and/or Map<> class/library? - Stack Overflow
Map実装例(ダメだった例)
はじめに、ダメだった例です。
いや、基本的な機能は実装できたのですが、格納した全要素にアクセスするときに困りました。
クラス名はHashSetにしてあります。
class HashSet<T> {
private items: { [key: string]: T; };
constructor() {
this.items = {};
}
add(key: string, value: T): void {
this.items[key] = value;
}
del(key: string): boolean {
return delete this.items[key];
}
has(key: string): boolean {
return key in this.items;
}
get(key: string): T {
return this.items[key];
}
len(): number {
return Object.keys(this.items).length;
}
}
function sampleF() {
var m :HashSet<string> = new HashSet<string>();
m.add("key1", "val1");
m.add("key2", "val2");
m.add("key3", "val3");
console.log(m);
for (let k in m) {
console.log(k + ":" + m.get(k));
}
}
(変数mの内容)
key1:val1
key2:val2
key3:val3
HashSet {items: Object}
items:undefined
add:undefined
del:undefined
has:undefined
get:undefined
len:undefined
アクセスしちゃいけない変数mの内部的な何かが……。forで変数mの要素をループしてるので当たり前か。
m.itemsのkey-valueペアたちが欲しいのに(´・ω・`)
itemsはprivateなclassメンバ変数なので、外から直接操作できませんね。
参考になりそうな記事やサイト その2
- TypeScriptで関数を型にする
- TypeScript でコールバック関数の型定義をしたい場合は、call signature を付けた interface として定義すれば良い。関数もオブジェクトの一種だから。
Map実装例(成功した例)
interface typFuncKeyVal{(key:string, val:any) :void};
class HashSet<T> {
private items: { [key: string]: T; };
constructor() {
this.items = {};
}
add(key: string, value: T): void {
this.items[key] = value;
}
del(key: string): boolean {
return delete this.items[key];
}
has(key: string): boolean {
return key in this.items;
}
get(key: string): T {
return this.items[key];
}
len(): number {
return Object.keys(this.items).length;
}
forEach(f:typFuncKeyVal) {
for (let k in this.items) {
f(k, this.items[k]);
}
}
}
function sampleF() {
var m :HashSet<string> = new HashSet<string>();
m.add("key1", "val1");
m.add("key2", "val2");
m.add("key3", "val3");
console.log(m);
// forEachにインターフェースを満たす関数を渡せる
m.forEach(function(k, v) {
console.log(k + ":" + v);
});
console.log("ついでに削除動作も確認");
console.log(m.len()); // 格納したkeyの数は?
m.del("key2");
console.log(m);
console.log(m.has("key2")); // 持ってるか?
console.log(m.get("key2")); // valueはどうなった?
console.log(m.get("none")); // そもそもいないkeyを指定したときの動作は?
console.log(m.len()); // 格納したkeyの数は?
m.forEach(function(k, v) {
console.log(k + ":" + v);
});
}
HashSet {items: Object}
key1:val1
key2:val2
key3:val3
ついでに削除動作も確認
3
HashSet {items: Object}
false
undefined
undefined
2
key1:val1
key3:val3
できました。
その他参考サイト
追記 2017/07/27
タイトルがあまりよくない気がしてきました。
ハッシュ値で比較してないので、似非HashMapでしたね。
-
Dictionary<TKey, TValue>
みたいなのもTypeScriptの練習をしていたら実現できたので、載せておきます- C#みたいに「.GetHashCode()でハッシュ値計算→.Equals()で同値判定」というのはやってません
- 数百件をブラウザでゴニョゴニョするだけなら問題ない体感速度で動きます
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map
を見ながらの習作です。
// イテレータパターンの実装1
class IterEntries<K,V> {
private pairs : Array<kv<K,V>>;
private i : number;
public value : kv<K,V>;
constructor(pairs : Array<kv<K,V>>) {
this.pairs = pairs;
this.i = 0;
this.value = pairs[0];
}
next() : IterEntries<K,V> {
this.i++;
this.value = this.pairs[this.i];
return this;
}
}
// イテレータパターンの実装2
class IterKeys<K> {
private keys : Array<K>;
private i : number;
public value : K;
constructor(keys : Array<K>) {
this.keys = keys;
this.i = 0;
this.value = keys[0];
}
// メソッドチェーンみたいに、.next().valueとしたいので、自身を返す
next() : IterKeys<K> {
this.i++;
this.value = this.keys[this.i];
return this;
}
}
// {key:◯◯◯, value:●●●}のインターフェース
interface kv<K,V> {
key : K,
value : V
}
// ES6のMapクラスっぽいものをES5で表現
// ただし、ジェネリックで実装してあるので、ES6のMapほど自由度は高くない
// あと、たぶん、ハッシュ値を格納してないので実行速度が遅い
class MapOld<K, V> {
private items :Array<kv<K,V>> // 擬似的に配列に格納
// コンストラクタ
constructor() {
this.items = [];
}
// 全要素の削除
clear() {
this.items = []
}
// 要素の削除
// 配列の要素を削除する別の方法としてdelete演算子があるが、これは要素数を減らさないため、厄介
// (参考)this.items.lengthが変化しない削除例 → delete this.items[i] // あくまでthis.items[i] = undefinedしてるだけ。
delete(k: K) {
for (let i = 0; i < this.items.length; i++) {
if (k === this.items[i].key) {
this.items.splice(i, 1);
break;
}
}
}
// key-valueのペアを配列にして返す
// イテレータパターン
entries() : IterEntries<K,V> {
let ret :Array<kv<K,V>> = [];
this.items.forEach(function (e) {
ret.push(e);
});
return new IterEntries<K,V>(ret);
}
// 全要素に関数を適用する
forEach(funcF :(value:V, key:K, map?:MapOld<K, V>)=>void) {
let retThis = this;
this.items.forEach(function (e) {
funcF(e.value, e.key, retThis);
});
}
// 要素の取得
// ES6のMapに比べて、内部でハッシュ値作れてないので線形探索
get(k: K): V {
let ret :V = null;
for (let i = 0; i < this.items.length; i++) {
if (k === this.items[i].key) {
ret = this.items[i].value;
break;
}
}
return ret;
}
// 要素の存在チェック
// ES6のMapに比べて、内部でハッシュ値作れてないので線形探索
has(k: K): boolean {
let ret :boolean = false;
for (let i = 0; i < this.items.length; i++) {
if (k === this.items[i].key) {
ret = true;
break;
}
}
return ret;
}
// keyを配列にして返す
// イテレータパターン
keys() : IterKeys<K> {
let ret :Array<K> = [];
this.items.forEach(function (e) {
ret.push(e.key);
});
return new IterKeys<K>(ret);
}
// 要素の追加
set(k: K, v: V): void {
this.items.push({key: k, value : v});
}
// 要素数の取得(ES6のMapにはないみたい)
len(): number {
return this.items.length;
}
}