397

この回答に関するアップデート3で明らかにされているように、この表記法は次のとおりです。

var hash = {};
hash[X]

実際にはオブジェクトをハッシュしませんX; 実際にXは、文字列に変換し(オブジェクトの場合、またはさまざまなプリミティブ型のその他の組み込み変換を介して.toString())、ハッシュせずにその文字列を「hash」で検索します。オブジェクトの同等性もチェックされません-2つの異なるオブジェクトが同じ文字列変換を持っている場合、それらは互いに上書きするだけです。

これを考えると、JavaScriptでのハッシュマップの効率的な実装はありますか?

(たとえば、2番目のGoogleの結果はjavascript hashmap、任意の操作に対してO(n)である実装を生成します。他のさまざまな結果は、同等の文字列表現を持つ異なるオブジェクトが互いに上書きするという事実を無視します。

4

17 に答える 17

412

オブジェクトを自分で手動でハッシュし、結果の文字列を通常の JavaScript 辞書のキーとして使用します。結局のところ、あなたは自分のオブジェクトがユニークである理由を知るのに最適な立場にいます. それが私がすることです。

例:

var key = function(obj){
  // Some unique object-dependent key
  return obj.totallyUniqueEmployeeIdKey; // Just an example
};

var dict = {};

dict[key(obj1)] = obj1;
dict[key(obj2)] = obj2;

このようにして、メモリ割り当てやオーバーフロー処理を重くすることなく、JavaScript によって行われるインデックス作成を制御できます。

もちろん、本当に「産業レベルのソリューション」が必要な場合は、キー関数によってパラメータ化されたクラスを構築し、コンテナの必要なすべての API を使用できますが、... JavaScript を使用し、シンプルで軽量になるように努めています。したがって、この機能的なソリューションはシンプルで高速です。

鍵関数は、オブジェクトの正しい属性を選択するのと同じくらい単純にすることができます。たとえば、鍵または鍵のセット (既に一意である)、鍵の組み合わせ (互いに一意である)、またはいくつかの暗号化ハッシュを使用するのと同じくらい複雑です。DojoX encodingまたはDojoX UUIDで。後者のソリューションは一意のキーを生成する可能性がありますが、個人的には、オブジェクトを一意にするものを知っている場合は特に、それらを絶対に避けようとします。

2014 年の更新: 2008 年に回答がありましたが、この単純な解決策にはまだ説明が必要です。Q&A形式でアイデアを明確にしましょう。

あなたのソリューションには実際のハッシュがありません。それはどこにある???

JavaScript は高級言語です。その基本プリミティブ ( Object ) には、プロパティを保持するためのハッシュ テーブルが含まれています。このハッシュ テーブルは通常、効率を高めるために低水準言語で記述されます。文字列キーを持つ単純なオブジェクトを使用して、効率的に実装されたハッシュ テーブルを使用します。

彼らがハッシュを使用していることをどのように知っていますか?

オブジェクトのコレクションをキーでアドレス指定できるようにするには、主に次の 3 つの方法があります。

  • 順不同。この場合、キーでオブジェクトを取得するには、すべてのキーを調べて、見つかったら停止する必要があります。平均して n/2 回の比較が必要です。
  • 順序付けられました。
    • 例 #1: 並べ替えられた配列 — 二分探索を行うと、平均で ~log2(n) 回の比較後にキーが見つかります。ずっといい。
    • 例 #2: 木。ここでも ~log(n) 回の試行になります。
  • ハッシュ表。平均して、一定の時間が必要です。比較: O(n) 対 O(log n) 対 O(1)。ブーム。

明らかに、JavaScript オブジェクトは何らかの形でハッシュ テーブルを使用して一般的なケースを処理します。

ブラウザベンダーは本当にハッシュテーブルを使用していますか???

本当。

彼らは衝突を処理しますか?

はい。上記を参照。等しくない文字列で衝突が見つかった場合は、躊躇せずにベンダーにバグを報告してください。

それで、あなたの考えは何ですか?

オブジェクトをハッシュしたい場合は、それを一意にするものを見つけてキーとして使用します。実際のハッシュを計算したり、ハッシュ テーブルをエミュレートしたりしないでください。基礎となる JavaScript オブジェクトによって既に効率的に処理されています。

このキーを JavaScript で使用Objectして、組み込みのハッシュ テーブルを活用しながら、デフォルト プロパティとの衝突の可能性を回避します。

開始するための例:

  • オブジェクトに一意のユーザー名が含まれている場合は、それをキーとして使用します。
  • 一意の顧客番号が含まれている場合は、それをキーとして使用します。
    • 米国のSSNやパスポート番号などの政府発行の一意の番号が含まれていて、システムで重複が許可されていない場合は、それをキーとして使用します。
  • フィールドの組み合わせが一意である場合は、それをキーとして使用します。
    • 米国の州の略語 + 運転免許証番号が優れたキーになります。
    • 国の略語 + パスポート番号も優れたキーです。
  • フィールドまたはオブジェクト全体の一部の関数は、一意の値を返すことができます — それをキーとして使用します。

あなたの提案を使用し、ユーザー名を使用してすべてのオブジェクトをキャッシュしました。しかし、組み込みプロパティである「toString」という名前の賢い人もいます。私は今どうすればいい?

明らかに、結果のキーがラテン文字のみで構成される可能性がわずかでもある場合は、それについて何かを行う必要があります。たとえば、「#toString」、「#MarySmith」などのデフォルト プロパティと衝突しないように、任意の非ラテン Unicode 文字を最初または最後に追加します。複合キーを使用する場合は、ラテン語以外の区切り文字「name,city,state」を使用してキー コンポーネントを区切ります。

一般に、これは創造性を発揮し、特定の制限 (一意性、デフォルト プロパティとの衝突の可能性) を伴う最も簡単なキーを選択する必要がある場所です。

注: 一意のキーは定義上衝突しませんが、潜在的なハッシュ衝突は基礎となる によって処理されObjectます。

産業用ソリューションが気に入らないのはなぜですか?

私見ですが、最良のコードはまったくコードがないことです。エラーがなく、メンテナンスが不要で、理解しやすく、瞬時に実行されます。私が見た「JavaScript のハッシュ テーブル」はすべて 100 行以上のコードで、複数のオブジェクトが含まれていました。と比較してくださいdict[key] = value

もう 1 つのポイント: JavaScript とまったく同じ原始オブジェクトを使用して、既に実装されているものを実装することで、低水準言語で記述された原始オブジェクトのパフォーマンスを超えることさえ可能ですか?

キーなしでオブジェクトをハッシュしたい!

幸運なことに、 ECMAScript 6 (2015 年 6 月にリリース) ではmapset が定義されています。

定義から判断すると、オブジェクトのアドレスをキーとして使用できるため、人工的なキーなしでオブジェクトを即座に区別できます。2 つの異なるが同一のオブジェクトである OTOH は、別個のものとしてマッピングされます。

MDNからの比較の内訳:

オブジェクトは、キーを値に設定したり、それらの値を取得したり、キーを削除したり、キーに何かが格納されているかどうかを検出したりできるという点でマップに似ています。このため (そして組み込みの代替手段がなかったため)、オブジェクトは歴史的にマップとして使用されてきました。ただし、特定の場合に Map の使用を推奨する重要な違いがあります。

  • オブジェクトのキーは文字列とシンボルですが、関数、オブジェクト、プリミティブなど、マップの任意の値にすることができます。
  • Map のキーは順序付けされていますが、オブジェクトに追加されたキーは順序付けられていません。したがって、それを反復処理すると、Map オブジェクトは挿入順にキーを返します。
  • Map のサイズは size プロパティで簡単に取得できますが、Object のプロパティの数は手動で決定する必要があります。
  • Map は iterable であるため、直接反復できますが、Object を反復するには、何らかの方法でキーを取得し、それらを反復する必要があります。
  • オブジェクトにはプロトタイプがあるため、注意しないとキーと衝突する可能性があるデフォルト キーがマップに存在します。ES5 の時点では、これは map = Object.create(null) を使用してバイパスできますが、これはめったに行われません。
  • キー ペアの頻繁な追加と削除を伴うシナリオでは、Map のパフォーマンスが向上する場合があります。
于 2008-12-15T14:21:15.487 に答える
176

問題の説明

JavaScriptには、任意のキーで任意の値にアクセスできる組み込みの一般的なマップタイプ(連想配列またはディクショナリと呼ばれることもあります)はありません。JavaScriptの基本的なデータ構造は、オブジェクトです。これは、文字列のみをキーとして受け入れ、プロトタイプの継承、ゲッターとセッター、さらにいくつかのブードゥーなどの特別なセマンティクスを持つ特別なタイプのマップです。

オブジェクトをマップとして使用する場合、キーはを介して文字列値に変換されることを覚えておく必要がありますtoString()。これにより、マッピング5と同じ値、およびメソッドをでインデックス付けされた値に'5'上書きしないすべてのオブジェクトになります。チェックしないと、継承されたプロパティに意図せずにアクセスする可能性もあります。toString()'[object Object]'hasOwnProperty()

JavaScriptの組み込み配列型は1ビットには役立ちません。JavaScript配列は連想配列ではなく、いくつかの特別なプロパティを持つオブジェクトにすぎません。マップとして使用できない理由を知りたい場合は、こちらをご覧ください

ユージーンのソリューション

Eugene Lazutkinは、カスタムハッシュ関数を使用して、関連する値を辞書オブジェクトのプロパティとして検索するために使用できる一意の文字列を生成するという基本的な考え方についてすでに説明しました。オブジェクトは内部でハッシュテーブルとして実装されているため、これが最も高速なソリューションになる可能性があります。

  • 注:ハッシュテーブル(ハッシュマップと呼ばれることもあります)は、バッキング配列と数値ハッシュ値によるルックアップを使用したマップコンセプトの特定の実装です。ランタイム環境では、JavaScriptオブジェクトを実装するために他の構造(検索ツリースキップリストなど)を使用する場合がありますが、オブジェクトは基本的なデータ構造であるため、十分に最適化する必要があります。

任意のオブジェクトの一意のハッシュ値を取得するための1つの可能性は、グローバルカウンターを使用して、ハッシュ値をオブジェクト自体(たとえば、という名前のプロパティ__hash)にキャッシュすることです。

これを実行するハッシュ関数は、プリミティブ値とオブジェクトの両方で機能します。

function hash(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
}

hash.current = 0;

この関数は、Eugeneの説明に従って使用できます。便宜上、さらにMapクラスでラップします。

私のMap実装

次の実装では、キーと値の両方を高速に反復できるように、キーと値のペアを二重にリンクされたリストに追加で格納します。hash()独自のハッシュ関数を提供するために、作成後にインスタンスのメソッドを上書きできます。

// Linking the key-value-pairs is optional.
// If no argument is provided, linkItems === undefined, i.e. !== false
// --> linking will be enabled
function Map(linkItems) {
    this.current = undefined;
    this.size = 0;

    if(linkItems === false)
        this.disableLinking();
}

Map.noop = function() {
    return this;
};

Map.illegal = function() {
    throw new Error("illegal operation for maps without linking");
};

// Map initialisation from an existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
    var map = new Map;

    for(var prop in obj) {
        if(foreignKeys || obj.hasOwnProperty(prop))
            map.put(prop, obj[prop]);
    }

    return map;
};

Map.prototype.disableLinking = function() {
    this.link = Map.noop;
    this.unlink = Map.noop;
    this.disableLinking = Map.noop;
    this.next = Map.illegal;
    this.key = Map.illegal;
    this.value = Map.illegal;
    this.removeAll = Map.illegal;

    return this;
};

// Overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
};

Map.prototype.hash.current = 0;

// --- Mapping functions

Map.prototype.get = function(key) {
    var item = this[this.hash(key)];
    return item === undefined ? undefined : item.value;
};

Map.prototype.put = function(key, value) {
    var hash = this.hash(key);

    if(this[hash] === undefined) {
        var item = { key : key, value : value };
        this[hash] = item;

        this.link(item);
        ++this.size;
    }
    else this[hash].value = value;

    return this;
};

Map.prototype.remove = function(key) {
    var hash = this.hash(key);
    var item = this[hash];

    if(item !== undefined) {
        --this.size;
        this.unlink(item);

        delete this[hash];
    }

    return this;
};

// Only works if linked
Map.prototype.removeAll = function() {
    while(this.size)
        this.remove(this.key());

    return this;
};

// --- Linked list helper functions

Map.prototype.link = function(item) {
    if(this.size == 0) {
        item.prev = item;
        item.next = item;
        this.current = item;
    }
    else {
        item.prev = this.current.prev;
        item.prev.next = item;
        item.next = this.current;
        this.current.prev = item;
    }
};

Map.prototype.unlink = function(item) {
    if(this.size == 0)
        this.current = undefined;
    else {
        item.prev.next = item.next;
        item.next.prev = item.prev;
        if(item === this.current)
            this.current = item.next;
    }
};

// --- Iterator functions - only work if map is linked

Map.prototype.next = function() {
    this.current = this.current.next;
};

Map.prototype.key = function() {
    return this.current.key;
};

Map.prototype.value = function() {
    return this.current.value;
};

次のスクリプト、

var map = new Map;

map.put('spam', 'eggs').
    put('foo', 'bar').
    put('foo', 'baz').
    put({}, 'an object').
    put({}, 'another object').
    put(5, 'five').
    put(5, 'five again').
    put('5', 'another five');

for(var i = 0; i++ < map.size; map.next())
    document.writeln(map.hash(map.key()) + ' : ' + map.value());

この出力を生成します:

string spam : eggs
string foo : baz
object 1 : an object
object 2 : another object
number 5 : five again
string 5 : another five

さらなる考慮事項

PEZはtoString()、おそらくハッシュ関数を使用して、メソッドを上書きすることを提案しました。プリミティブ値では機能しないため、これは実行可能ではありません(toString()プリミティブに変更することは非常に悪い考えです)。toString()任意のオブジェクトに意味のある値を返したい場合は、を変更する必要があります。これはObject.prototype、一部の人(私自身は含まれていません)が冗長と見なします。


私の実装の現在のバージョンとMap他のJavaScriptの機能は、ここから入手できます

于 2008-12-20T17:57:30.630 に答える
39

ECMAScript 2015 (ES6) によると、標準の JavaScript には Map 実装があります。詳細については、こちらを参照してください。

基本的な使い方:

var myMap = new Map();
var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};

// Setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");

myMap.size; // 3

// Getting the values
myMap.get(keyString);    // "value associated with 'a string'"
myMap.get(keyObj);       // "value associated with keyObj"
myMap.get(keyFunc);      // "value associated with keyFunc"
于 2015-11-23T08:25:57.200 に答える
29

Javaマップに似たものを使用する簡単で便利な方法は次のとおりです。

var map = {
              'map_name_1': map_value_1,
              'map_name_2': map_value_2,
              'map_name_3': map_value_3,
              'map_name_4': map_value_4
          }

そして、値を取得するには:

alert(map['map_name_1']);    // Gives the value of map_value_1

... etc. ...
于 2012-07-10T11:32:50.927 に答える
13

オブジェクトと値のペアのカプレットを内部状態で保存する必要があります。

HashMap = function(){
  this._dict = [];
}

HashMap.prototype._get = function(key){
  for(var i=0, couplet; couplet = this._dict[i]; i++){
    if(couplet[0] === key){
      return couplet;
    }
  }
}

HashMap.prototype.put = function(key, value){
  var couplet = this._get(key);
  if(couplet){
    couplet[1] = value;
  }else{
    this._dict.push([key, value]);
  }
  return this; // for chaining
}
HashMap.prototype.get = function(key){
  var couplet = this._get(key);
  if(couplet){
    return couplet[1];
  }
}

そしてそれを次のように使用します:

var color = {}; // Unique object instance
var shape = {}; // Unique object instance
var map = new HashMap();
map.put(color, "blue");
map.put(shape, "round");
console.log("Item is", map.get(color), "and", map.get(shape));

もちろん、この実装も O(n) に沿ったものです。Eugene の例は、実際のハッシュに期待されるあらゆる種類の速度で機能するハッシュを取得する唯一の方法です。

ユージーンの答えに沿った別のアプローチは、何らかの方法ですべてのオブジェクトに一意の ID をアタッチすることです。私のお気に入りのアプローチの 1 つは、Object スーパークラスから継承された組み込みメソッドの 1 つを取得し、それをカスタム関数パススルーに置き換えて、その関数オブジェクトにプロパティをアタッチすることです。これを行うために HashMap メソッドを書き直すと、次のようになります。

HashMap = function(){
  this._dict = {};
}

HashMap.prototype._shared = {id: 1};
HashMap.prototype.put = function put(key, value){
  if(typeof key == "object"){
    if(!key.hasOwnProperty._id){
      key.hasOwnProperty = function(key){
        return Object.prototype.hasOwnProperty.call(this, key);
      }
      key.hasOwnProperty._id = this._shared.id++;
    }
    this._dict[key.hasOwnProperty._id] = value;
  }else{
    this._dict[key] = value;
  }
  return this; // for chaining
}

HashMap.prototype.get = function get(key){
  if(typeof key == "object"){
    return this._dict[key.hasOwnProperty._id];
  }
  return this._dict[key];
}

このバージョンはわずかに高速であるように見えますが、理論的には大規模なデータ セットの場合は大幅に高速になります。

于 2008-12-15T16:54:14.133 に答える
11

残念ながら、以前の回答はどれも私の場合には適切ではありませんでした: 異なるキー オブジェクトが同じハッシュ コードを持っている可能性があります。したがって、単純な Java ライクな HashMap バージョンを作成しました。

function HashMap() {
    this.buckets = {};
}

HashMap.prototype.put = function(key, value) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        bucket = new Array();
        this.buckets[hashCode] = bucket;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            bucket[i].value = value;
            return;
        }
    }
    bucket.push({ key: key, value: value });
}

HashMap.prototype.get = function(key) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        return null;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            return bucket[i].value;
        }
    }
}

HashMap.prototype.keys = function() {
    var keys = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            keys.push(bucket[i].key);
        }
    }
    return keys;
}

HashMap.prototype.values = function() {
    var values = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            values.push(bucket[i].value);
        }
    }
    return values;
}

注: キー オブジェクトは、hashCode() および equals() メソッドを「実装」する必要があります。

于 2011-04-07T07:25:11.513 に答える
6

コードをhttp://github.com/lambder/HashMapJS/tree/masterから取得できるJavaScriptHashMapを実装しました

コードは次のとおりです。

/*
 =====================================================================
 @license MIT
 @author Lambder
 @copyright 2009 Lambder.
 @end
 =====================================================================
 */
var HashMap = function() {
  this.initialize();
}

HashMap.prototype = {
  hashkey_prefix: "<#HashMapHashkeyPerfix>",
  hashcode_field: "<#HashMapHashkeyPerfix>",

  initialize: function() {
    this.backing_hash = {};
    this.code = 0;
  },
  /*
   Maps value to key returning previous association
   */
  put: function(key, value) {
    var prev;
    if (key && value) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        prev = this.backing_hash[hashCode];
      } else {
        this.code += 1;
        hashCode = this.hashkey_prefix + this.code;
        key[this.hashcode_field] = hashCode;
      }
      this.backing_hash[hashCode] = value;
    }
    return prev;
  },
  /*
   Returns value associated with given key
   */
  get: function(key) {
    var value;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        value = this.backing_hash[hashCode];
      }
    }
    return value;
  },
  /*
   Deletes association by given key.
   Returns true if the association existed, false otherwise
   */
  del: function(key) {
    var success = false;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        var prev = this.backing_hash[hashCode];
        this.backing_hash[hashCode] = undefined;
        if(prev !== undefined)
          success = true;
      }
    }
    return success;
  }
}

//// Usage

// Creation

var my_map = new HashMap();

// Insertion

var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};

my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);

// Retrieval

if(my_map.get(a_key) !== a_value){
  throw("fail1")
}
if(my_map.get(b_key) !== c_value){
  throw("fail2")
}
if(prev_b !== b_value){
  throw("fail3")
}

// Deletion

var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);

if(a_existed !== true){
  throw("fail4")
}
if(c_existed !== false){
  throw("fail5")
}
if(a2_existed !== false){
  throw("fail6")
}
于 2009-07-09T20:20:52.600 に答える
5

ECMAScript 6 では、WeakMapを使用できます。

例:

var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // A value can be anything, including an object or a function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // Keys and values can be any objects. Even WeakMaps!

wm1.get(o2); // "azerty"
wm2.get(o2); // Undefined, because there is no value for o2 on wm2
wm2.get(o3); // Undefined, because that is the set value

wm1.has(o2); // True
wm2.has(o2); // False
wm2.has(o3); // True (even if the value itself is 'undefined')

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // Undefined, because wm3 was cleared and there is no value for o1 anymore

wm1.has(o1);   // True
wm1.delete(o1);
wm1.has(o1);   // False

しかし:

参照が弱いため、WeakMap キーは列挙できません (つまり、キーのリストを提供するメソッドはありません)。

于 2013-11-12T07:33:30.620 に答える
3

JavaScript ハッシュ テーブルの実装を試してください: http://www.timdown.co.uk/jsashtable

キー オブジェクトの hashCode() メソッドを探します。または、Hashtable オブジェクトの作成時にハッシュ関数を指定できます。

于 2009-05-27T10:40:26.843 に答える
2

_hashパフォーマンスが重要ではなく (たとえば、キーの量が比較的少ない)、オブジェクトを、 などの追加フィールドで汚染したくない場合 (または、オブジェクトを汚染したくない場合) _id、次の事実を利用できます。Array.prototype.indexOf厳密な平等を採用しています。簡単な実装を次に示します。

var Dict = (function(){
    // Internet Explorer 8 and earlier does not have any Array.prototype.indexOf
    function indexOfPolyfill(val) {
      for (var i = 0, l = this.length; i < l; ++i) {
        if (this[i] === val) {
          return i;
        }
      }
      return -1;
    }

    function Dict(){
      this.keys = [];
      this.values = [];
      if (!this.keys.indexOf) {
        this.keys.indexOf = indexOfPolyfill;
      }
    };

    Dict.prototype.has = function(key){
      return this.keys.indexOf(key) != -1;
    };

    Dict.prototype.get = function(key, defaultValue){
      var index = this.keys.indexOf(key);
      return index == -1 ? defaultValue : this.values[index];
    };

    Dict.prototype.set = function(key, value){
      var index = this.keys.indexOf(key);
      if (index == -1) {
        this.keys.push(key);
        this.values.push(value);
      } else {
        var prevValue = this.values[index];
        this.values[index] = value;
        return prevValue;
      }
    };

    Dict.prototype.delete = function(key){
      var index = this.keys.indexOf(key);
      if (index != -1) {
        this.keys.splice(index, 1);
        return this.values.splice(index, 1)[0];
      }
    };

    Dict.prototype.clear = function(){
      this.keys.splice(0, this.keys.length);
      this.values.splice(0, this.values.length);
    };

    return Dict;
})();

使用例:

var a = {}, b = {},
    c = { toString: function(){ return '1'; } },
    d = 1, s = '1', u = undefined, n = null,
    dict = new Dict();

// Keys and values can be anything
dict.set(a, 'a');
dict.set(b, 'b');
dict.set(c, 'c');
dict.set(d, 'd');
dict.set(s, 's');
dict.set(u, 'u');
dict.set(n, 'n');

dict.get(a); // 'a'
dict.get(b); // 'b'
dict.get(s); // 's'
dict.get(u); // 'u'
dict.get(n); // 'n'
// etc.

deleteECMAScript 6 WeakMap と比較すると、O(n) 検索時間と非脆弱性 (つまり、キーを使用またはclear解放しないとメモリ リークが発生する) という 2 つの問題があります。

于 2013-11-20T23:11:36.757 に答える
2

JavaScript にはマップ/ハッシュマップが組み込まれていません。連想配列と呼ぶべきです。

hash["X"]は と同じですがhash.X、文字列変数として "X" を使用できます。つまり、hash[x]機能的には と同等eval("hash."+x.toString())です。

キーと値のマッピングというよりは、object.properties に似ています。JavaScript でより適切なキー/値マッピングを探している場合は、Map オブジェクトを使用してください。

于 2008-12-21T15:07:01.003 に答える
2

クリストフの例から派生した私の「マップ」実装:

使用例:

var map = new Map();  // Creates an "in-memory" map
var map = new Map("storageId");  // Creates a map that is loaded/persisted using html5 storage

function Map(storageId) {
    this.current = undefined;
    this.size = 0;
    this.storageId = storageId;
    if (this.storageId) {
        this.keys = new Array();
        this.disableLinking();
    }
}

Map.noop = function() {
    return this;
};

Map.illegal = function() {
    throw new Error("illegal operation for maps without linking");
};

// Map initialisation from an existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
    var map = new Map;
    for(var prop in obj) {
        if(foreignKeys || obj.hasOwnProperty(prop))
            map.put(prop, obj[prop]);
    }
    return map;
};

Map.prototype.disableLinking = function() {
    this.link = Map.noop;
    this.unlink = Map.noop;
    this.disableLinking = Map.noop;

    this.next = Map.illegal;
    this.key = Map.illegal;
    this.value = Map.illegal;
//    this.removeAll = Map.illegal;


    return this;
};

// Overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
};

Map.prototype.hash.current = 0;

// --- Mapping functions

Map.prototype.get = function(key) {
    var item = this[this.hash(key)];
    if (item === undefined) {
        if (this.storageId) {
            try {
                var itemStr = localStorage.getItem(this.storageId + key);
                if (itemStr && itemStr !== 'undefined') {
                    item = JSON.parse(itemStr);
                    this[this.hash(key)] = item;
                    this.keys.push(key);
                    ++this.size;
                }
            } catch (e) {
                console.log(e);
            }
        }
    }
    return item === undefined ? undefined : item.value;
};

Map.prototype.put = function(key, value) {
    var hash = this.hash(key);

    if(this[hash] === undefined) {
        var item = { key : key, value : value };
        this[hash] = item;

        this.link(item);
        ++this.size;
    }
    else this[hash].value = value;
    if (this.storageId) {
        this.keys.push(key);
        try {
            localStorage.setItem(this.storageId + key, JSON.stringify(this[hash]));
        } catch (e) {
            console.log(e);
        }
    }
    return this;
};

Map.prototype.remove = function(key) {
    var hash = this.hash(key);
    var item = this[hash];
    if(item !== undefined) {
        --this.size;
        this.unlink(item);

        delete this[hash];
    }
    if (this.storageId) {
        try {
            localStorage.setItem(this.storageId + key, undefined);
        } catch (e) {
            console.log(e);
        }
    }
    return this;
};

// Only works if linked
Map.prototype.removeAll = function() {
    if (this.storageId) {
        for (var i=0; i<this.keys.length; i++) {
            this.remove(this.keys[i]);
        }
        this.keys.length = 0;
    } else {
        while(this.size)
            this.remove(this.key());
    }
    return this;
};

// --- Linked list helper functions

Map.prototype.link = function(item) {
    if (this.storageId) {
        return;
    }
    if(this.size == 0) {
        item.prev = item;
        item.next = item;
        this.current = item;
    }
    else {
        item.prev = this.current.prev;
        item.prev.next = item;
        item.next = this.current;
        this.current.prev = item;
    }
};

Map.prototype.unlink = function(item) {
    if (this.storageId) {
        return;
    }
    if(this.size == 0)
        this.current = undefined;
    else {
        item.prev.next = item.next;
        item.next.prev = item.prev;
        if(item === this.current)
            this.current = item.next;
    }
};

// --- Iterator functions - only work if map is linked

Map.prototype.next = function() {
    this.current = this.current.next;
};

Map.prototype.key = function() {
    if (this.storageId) {
        return undefined;
    } else {
        return this.current.key;
    }
};

Map.prototype.value = function() {
    if (this.storageId) {
        return undefined;
    }
    return this.current.value;
};
于 2012-07-04T00:19:46.877 に答える
1

これはかなり堅牢なソリューションのように見えます: https://github.com/flesler/hashmap

同じように見える関数やオブジェクトでもうまく機能します。それが使用する唯一のハックは、オブジェクトを識別するためにあいまいなメンバーをオブジェクトに追加することです。プログラムがそのあいまいな変数 ( hashidのようなもの) を上書きしない場合、あなたは成功しています。

于 2013-08-26T05:16:13.247 に答える