LoginSignup
6
3

More than 5 years have passed since last update.

TypeScriptにHashMapを実装

Last updated at Posted at 2016-12-04

動機

コレクション(C#でいうHashSetやList)が欲しいことってありますよね。
ES6であれば、JavaScriptにMapクラスが提供されているので独自実装する必要はないのですが、
ES5にはないので、Mapクラスを自作する必要があります。
※友人AのMacのSafariでページの一部(ES6のMapクラス使ってる部分)が動作しないと苦情が。対応せねば。
※友人BのWindows VistaのレガシーなIEでほぼ動かないと苦情が。これは黙殺します。

参考になりそうな記事やサイト

Map実装例(ダメだった例)

はじめに、ダメだった例です。
いや、基本的な機能は実装できたのですが、格納した全要素にアクセスするときに困りました。
クラス名はHashSetにしてあります。

クラス定義部分.ts
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;
    }
}
検証部分.ts
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));
    }
}
欲しかった結果.log
(変数mの内容)
key1:val1
key2:val2
key3:val3
実際に得られた結果.log
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

Map実装例(成功した例)

クラス定義部分_改.ts
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]);
        }
    }
}
検証部分_改.ts
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);
    });
}
実際に得られた結果_改.log
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
を見ながらの習作です。

MapOld.ts
// イテレータパターンの実装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;
    }
}
6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3