web-dev-qa-db-fra.com

Javascript HashTable utilise la clé d'objet

Je veux créer une table de hachage qui obtient mon objet comme clé sans le convertir en chaîne.

Quelque chose comme ça:

var object1 = new Object();
var object2 = new Object();

var myHash = new HashTable();

myHash.put(object1, "value1");
myHash.put(object2, "value2");

alert(myHash.get(object1), myHash.get(object2)); // I wish that it will print value1 value2

EDIT: Voir ma réponse pour une solution complète

37
Ilya Gazman

Voici une proposition:

function HashTable() {
    this.hashes = {};
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( key, value ) {
        this.hashes[ JSON.stringify( key ) ] = value;
    },

    get: function( key ) {
        return this.hashes[ JSON.stringify( key ) ];
    }
};

L'API est exactement comme indiqué dans votre question.

Cependant, vous ne pouvez pas jouer avec la référence dans js (donc deux objets vides ressembleront à la table de hachage), car vous n'avez aucun moyen de l'obtenir. Voir cette réponse pour plus de détails: Comment obtenir les références d'objet javascript ou le nombre de références?

Démo Jsfiddle: http://jsfiddle.net/HKz3e/

Cependant, pour le côté unique des choses, vous pouvez jouer avec les objets originaux, comme de cette façon:

function HashTable() {
    this.hashes = {},
    this.id = 0;
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( obj, value ) {
        obj.id = this.id;
        this.hashes[ this.id ] = value;
        this.id++;
    },

    get: function( obj ) {
        return this.hashes[ obj.id ];
    }
};

Démo Jsfiddle: http://jsfiddle.net/HKz3e/2/

Cela signifie que vos objets doivent avoir une propriété nommée id que vous n'utiliserez pas ailleurs. Si vous voulez que cette propriété soit non énumérable, je vous suggère de jeter un coup d'œil à defineProperty (ce n'est pas un navigateur croisé cependant, même avec ES5-Shim, il ne le fait pas travailler dans IE7).

Cela signifie également que vous êtes limité sur le nombre d'articles que vous pouvez stocker dans cette table de hachage. Limité à 253 , c'est-à-dire.

Et maintenant, la solution "ça ne marchera nulle part": utilisez ES6 WeakMaps. Ils sont faits exactement dans ce but: avoir des objets comme clés. Je vous suggère de lire MDN pour plus d'informations: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/WeakMap

Elle diffère cependant légèrement de votre API (c'est set et non put):

var myMap = new WeakMap(),
    object1 = {},
    object2 = {};

myMap.set( object1, 'value1' );
myMap.set( object2, 'value2' );

console.log( myMap.get( object1 ) ); // "value1"
console.log( myMap.get( object2 ) ); // "value2"

Démo Jsfiddle avec un shim de carte faible: http://jsfiddle.net/Ralt/HKz3e/9/

Cependant, les cartes faibles sont implémentées dans FF et Chrome ( uniquement si vous activez l'indicateur "Fonctionnalités javascript expérimentales" dans = chrome cependant). Il existe des cales disponibles, comme celle-ci: https://Gist.github.com/1269991 . Utilisez à vos risques et périls.

Vous pouvez également utiliser Maps, ils peuvent mieux convenir à vos besoins, car vous devez également stocker des valeurs primitives (chaînes) sous forme de clés. Doc , Shim .

18
Florian Margaine

Voici une implémentation simple de Map qui fonctionnera avec n'importe quel type de clé, y compris les références d'objet, et ne mutera en aucune façon la clé:

function Map() {
    var keys = [], values = [];

    return {
        put: function (key, value) {
            var index = keys.indexOf(key);
            if(index == -1) {
                keys.Push(key);
                values.Push(value);
            }
            else {
                values[index] = value;
            }
        },
        get: function (key) {
            return values[keys.indexOf(key)];
        }
    };
}

Bien que cela donne les mêmes fonctionnalités qu'une table de hachage, elle n'est pas réellement implémentée à l'aide d'une fonction de hachage car elle itère sur les tableaux et présente les performances les plus défavorables de O (n). Cependant, pour la grande majorité des cas d'utilisation raisonnables, cela ne devrait pas être un problème du tout. La fonction indexOf est implémentée par le moteur JavaScript et est hautement optimisée.

22
Peter

J'ai porté la suggestion de @Florian Margaine à un niveau supérieur et j'ai trouvé ceci:

function HashTable(){
    var hash = new Object();
    this.put = function(key, value){
        if(typeof key === "string"){
            hash[key] = value;
        }
        else{
            if(key._hashtableUniqueId == undefined){
                key._hashtableUniqueId = UniqueId.prototype.generateId();
            }
            hash[key._hashtableUniqueId] = value;
        }

    };

    this.get = function(key){
        if(typeof key === "string"){
            return hash[key];
        }
        if(key._hashtableUniqueId == undefined){
            return undefined;
        }
        return hash[key._hashtableUniqueId];
    };
}

function UniqueId(){

}

UniqueId.prototype._id = 0;
UniqueId.prototype.generateId = function(){
    return (++UniqueId.prototype._id).toString();
};

tilisation

var map = new HashTable();
var object1 = new Object();
map.put(object1, "Cocakola");
alert(map.get(object1)); // Cocakola

//Overriding
map.put(object1, "Cocakola 2");
alert(map.get(object1)); // Cocakola 2

// String key is used as String     
map.put("myKey", "MyValue");
alert(map.get("myKey")); // MyValue
alert(map.get("my".concat("Key"))); // MyValue

// Invalid keys 
alert(map.get("unknownKey")); // undefined
alert(map.get(new Object())); // undefined
11
Ilya Gazman

Voici une proposition, combinant la solution de @ Florian avec celle de @ Laurent.

function HashTable() {
    this.hashes = [];
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( key, value ) {
        this.hashes.Push({
            key: key,
            value: value
        });
    },

    get: function( key ) {
        for( var i = 0; i < this.hashes.length; i++ ){
            if(this.hashes[i].key == key){
                return this.hashes[i].value;
            }
        }
    }
};

Il ne changera en aucun cas votre objet et ne dépend pas de JSON.stringify.

2
ipodppod

Je sais que j'ai un an de retard, mais pour tous ceux qui tombent sur ce fil, j'ai écrit l'objet ordonné stringify à JSON, qui résout le dilemme noté ci-dessus: http://stamat.wordpress.com/javascript-object-order-property-stringify /

Je jouais également avec des implémentations de table de hachage personnalisées qui sont également liées au sujet: http://stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array /

//SORT WITH STRINGIFICATION

var orderedStringify = function(o, fn) {
    var props = [];
    var res = '{';
    for(var i in o) {
        props.Push(i);
    }
    props = props.sort(fn);

    for(var i = 0; i < props.length; i++) {
        var val = o[props[i]];
        var type = types[whatis(val)];
        if(type === 3) {
            val = orderedStringify(val, fn);
        } else if(type === 2) {
            val = arrayStringify(val, fn);
        } else if(type === 1) {
            val = '"'+val+'"';
        }

        if(type !== 4)
            res += '"'+props[i]+'":'+ val+',';
    }

    return res.substring(res, res.lastIndexOf(','))+'}';
};

//orderedStringify for array containing objects
var arrayStringify = function(a, fn) {
    var res = '[';
    for(var i = 0; i < a.length; i++) {
        var val = a[i];
        var type = types[whatis(val)];
        if(type === 3) {
            val = orderedStringify(val, fn);
        } else if(type === 2) {
            val = arrayStringify(val);
        } else if(type === 1) {
            val = '"'+val+'"';
        }

        if(type !== 4)
            res += ''+ val+',';
    }

    return res.substring(res, res.lastIndexOf(','))+']';
}
1
stamat

Utilisez simplement l'opérateur d'égalité stricte lorsque vous recherchez l'objet: ===

var objects = [];
objects.Push(object1);
objects.Push(object2);

objects[0] === object1; // true
objects[1] === object1; // false

L'implémentation dépendra de la façon dont vous stockez les objets dans la classe HashTable.

0
laurent

La meilleure solution consiste à utiliser WeakMap quand vous le pouvez (c'est-à-dire lorsque vous ciblez les navigateurs le supportant)

Sinon, vous pouvez utiliser la solution de contournement suivante (écrite en TypeScript et sécurisée contre les collisions):

// Run this in the beginning of your app (or put it into a file you just import)
(enableObjectID)();

const uniqueId: symbol = Symbol('The unique id of an object');

function enableObjectID(): void {
    if (typeof Object['id'] !== 'undefined') {
        return;
    }

    let id: number = 0;

    Object['id'] = (object: any) => {
        const hasUniqueId: boolean = !!object[uniqueId];
        if (!hasUniqueId) {
            object[uniqueId] = ++id;
        }

        return object[uniqueId];
    };
}

Ensuite, vous pouvez simplement obtenir un numéro unique pour n'importe quel objet de votre code (comme si cela aurait été pour l'adresse du pointeur)

let objectA = {};
let objectB = {};
let dico = {};

dico[(<any>Object).id(objectA)] = "value1";

// or 

dico[Object['id'](objectA);] = "value1";

// If you are not using TypeScript you don't need the casting

dico[Object.id(objectA)] = "value1"
0
Flavien Volken

Inspiré par @florian, voici un moyen où l'ID n'a pas besoin de JSON.stringify:

'use strict';

module.exports = HashTable;

function HashTable () {
  this.index = [];
  this.table = [];
}

HashTable.prototype = {

  constructor: HashTable,

  set: function (id, key, value) {
    var index = this.index.indexOf(id);
    if (index === -1) {
      index = this.index.length;
      this.index.Push(id);
      this.table[index] = {};
    }
    this.table[index][key] = value;
  },

  get: function (id, key) {
    var index = this.index.indexOf(id);
    if (index === -1) {
      return undefined;
    }
    return this.table[index][key];
  }

};
0
webjay

J'ai pris la solution @Ilya_Gazman et l'ai améliorée en définissant '_hashtableUniqueId' comme une propriété non énumérable (elle n'apparaîtra pas dans les requêtes JSON ni ne sera répertoriée dans les boucles for). Objet UniqueId également supprimé, car il suffit d'utiliser uniquement la fermeture de la fonction HastTable. Pour plus de détails sur l'utilisation, veuillez consulter le post Ilya_Gazman

function HashTable() {
   var hash = new Object();

   return {
       put: function (key, value) {
           if(!HashTable.uid){
               HashTable.uid = 0;
           }
           if (typeof key === "string") {
               hash[key] = value;
           } else {
               if (key._hashtableUniqueId === undefined) {
                   Object.defineProperty(key, '_hashtableUniqueId', {
                       enumerable: false,
                       value: HashTable.uid++
                   });
               }
               hash[key._hashtableUniqueId] = value;
           }
       },
       get: function (key) {
           if (typeof key === "string") {
               return hash[key];
           }
           if (key._hashtableUniqueId === undefined) {
               return undefined;
           }
           return hash[key._hashtableUniqueId];
       }
   };
}
0
edrian

Basé sur la réponse de Peters, mais avec une conception de classe appropriée (sans abuser des fermetures), les valeurs sont donc déboguables. Renommé de Map à ObjectMap, car Map est une fonction intégrée. Ajout de la méthode exists:

ObjectMap = function() {
    this.keys = [];
    this.values = [];
}

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

ObjectMap.prototype.get = function(key) {
    return this.values[ this.keys.indexOf(key) ];
}

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

/*
    TestObject = function() {}

    testA = new TestObject()
    testB = new TestObject()

    om = new ObjectMap()
    om.set(testA, true)
    om.get(testB)
    om.exists(testB)
    om.exists(testA)
    om.exists(testB)
*/
0
lama12345

Utiliser JSON.stringify() est complètement gênant pour moi, et ne donne au client aucun contrôle réel sur la façon dont leurs clés sont identifiées de manière unique. Les objets qui sont utilisés comme clés devraient avoir une fonction de hachage, mais je suppose que dans la plupart des cas, le remplacement de la méthode toString(), afin qu'ils renvoient des chaînes uniques, fonctionnera correctement:

var myMap = {};

var myKey = { toString: function(){ return '12345' }};
var myValue = 6;

// same as myMap['12345']
myMap[myKey] = myValue;

De toute évidence, toString() devrait faire quelque chose de significatif avec les propriétés de l'objet pour créer une chaîne unique. Si vous souhaitez garantir que vos clés sont valides, vous pouvez créer un wrapper et dans les méthodes get() et put(), ajoutez une coche comme:

if(!key.hasOwnProperty('toString')){
   throw(new Error('keys must override toString()'));
}

Mais si vous allez faire autant de travail, vous pouvez tout aussi bien utiliser autre chose que toString(); quelque chose qui rend votre intention plus claire. Une proposition très simple serait donc:

function HashTable() {
    this.hashes = {};
}

HashTable.prototype = {
    constructor: HashTable,

    put: function( key, value ) {
        // check that the key is meaningful, 
        // also will cause an error if primitive type
        if( !key.hasOwnProperty( 'hashString' ) ){
           throw( new Error( 'keys must implement hashString()' ) );
        }
        // use .hashString() because it makes the intent of the code clear
        this.hashes[ key.hashString() ] = value;
    },

    get: function( key ) {
        // check that the key is meaningful, 
        // also will cause an error if primitive type
        if( !key.hasOwnProperty( 'hashString' ) ){
           throw( new Error( 'keys must implement hashString()' ) );
        }
        // use .hashString() because it make the intent of the code clear
        return this.hashes[ key.hashString()  ];
    }
};
0
sethro