Як робити асоціативний масив / хешування в JavaScript


574

Мені потрібно зберігати деякі статистичні дані за допомогою JavaScript так, як я б це робив у C #:

Dictionary<string, int> statistics;

statistics["Foo"] = 10;
statistics["Goo"] = statistics["Goo"] + 1;
statistics.Add("Zoo", 1);

Чи існує Hashtableщось подібне Dictionary<TKey, TValue>у JavaScript?
Як я міг зберігати значення таким чином?


1
js введено вільно, тому немає можливості просто оголосити рядок або int, ви можете просто оголосити var і призначити йому рядок або int. : D
Гордон Густафсон

Ви можете перевірити xDict. jsfiddle.net/very/MuVwd Це словник String => все, що написано в JavaScript.
Роберт

У цій статті є чудове пояснення того, як асоціативні масиви реалізуються під кришкою в Javascript jayconrod.com/posts/52/a-tour-of-v8-object-representation
Shuklaswag,

Прийнята відповідь була написана в 2009 році - вона підтримує лише рядкові клавіші. Для нерядкових ключів використовуйте Map або WeakMap, як у відповіді Віталія .
ToolmakerSteve

Відповіді:


564

Використовуйте об’єкти JavaScript як асоціативні масиви .

Асоціативний масив: простими словами асоціативні масиви використовують рядки замість чисел Integer як індекс.

Створіть об’єкт за допомогою

var dictionary = {};

Javascript дозволяє додавати властивості до об'єктів за допомогою наступного синтаксису:

Object.yourProperty = value;

Альтернативний синтаксис для того ж:

Object["yourProperty"] = value;

Якщо ви також можете створити ключ для значення об'єктних карт із наступним синтаксисом

var point = { x:3, y:2 };

point["x"] // returns 3
point.y // returns 2

Ви можете перебирати асоціативний масив, використовуючи конструкцію циклу for..in наступним чином

for(var key in Object.keys(dict)){
  var value = dict[key];
  /* use key/value for intended purpose */
}

36
Зауважимо, що авторський підхід до ініціалізації "асоціативного масиву" з new Array()насуплено. Стаття зрештою згадує свої недоліки та пропонує new Object()або {}як переважні альтернативи, але це вже наприкінці, і я побоююся, що більшість читачів не вдасться так далеко.
Даніель Любаров

23
Збій. JavaScript не підтримує посилання на об'єкти як ключі, тоді як щось подібне до Flash / AS3 Dictionary. У JavaScript, var obj1 = {}; var obj2 = {}; var table= {}; table[obj1] = "A"; table[obj2] = "B"; alert(table[obj1]); //displays Bоскільки він не може розрізняти ключі obj1 та obj2; вони обидва перетворюються на рядок і просто стають чимось на зразок "Об'єкт". Повний збій, а також робить безпечну для серіалізації типів з посиланнями та цикличними посиланнями неушкодженими або неефективними в JavaScript. У Flash / AS3 це легко.
Трінько

Ну, єдиний спосіб у JS ми можемо перевірити, перевіривши рівність або визначивши метод рівних таким чином: Point.prototype.equals = function(obj) { return (obj instanceof Point) && (obj.x === this.x) && (obj.y === this.y); };
Надей

1
@Leo console.log ({A: 'B', C: 'D'} [foo]) повинен дати вам B.
ychaouche

2
@Leo Приклад здається неправильним. for... inдля словника буде циклічно працювати над його клавішами, тому він, Object.keysздається, непрацездатний. Object.keysповертає масив ключів словника, а for... inдля масиву перетворює на його "ключі", які для масиву - це його індекси, а не його значення.
JHH

434
var associativeArray = {};
associativeArray["one"] = "First";
associativeArray["two"] = "Second";
associativeArray["three"] = "Third";

Якщо ви перебуваєте з об'єктно-орієнтованої мови, слід переглянути цю статтю .


38
Ви також можете зробити це в менших рядках: var associativeArray = {"one": "Перший", "два": "другий", "три": "Третій"}; Тоді AssociativeArray ["one"] повертає "First", а assocativeArray ["four"] повертає null.
Тоні Вікхем

2
Вибачте @JuusoOhtonen, я написав пост 6 років тому (неймовірно, як швидко проходить час). Я оновив посилання. Перевірте це і не соромтесь запитати, чи є у вас сумніви
Дані Крико

145

Усі сучасні браузери підтримують об’єкт javascript Map . Є кілька причин, завдяки яким використання карт краще, ніж об’єкт:

  • Об'єкт має прототип, тому на карті є ключі за замовчуванням.
  • Ключі об’єкта - це рядки, де вони можуть бути будь-яким значенням для карти.
  • Ви можете легко отримати розмір карти, тоді як вам слід буде відстежувати розмір Об'єкта.

Приклад:

var myMap = new Map();

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

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

myMap.get(keyString);    // "value associated with 'a string'"
myMap.get(keyObj);       // "value associated with keyObj"
myMap.get(keyFunc);      // "value associated with keyFunc"

Якщо ви хочете, щоб ключі, на які не посилаються інші об’єкти, збирали сміття, розгляньте можливість використання WeakMap замість Map.


5
Сподіваємось, через кілька років це буде найбільше голосів за відповідь.
Камерон Лі

1
@CameronLee неодмінно буде
Loïc Faure-Lacroix

1
Це Mapледве корисно, коли ваш ключ є об’єктом, але його слід порівнювати за значенням, а не посиланням.
Сіюань Рен

7
Більше року після написання цієї відповіді все ще НЕ вірно, що "всі сучасні браузери підтримують карту". Тільки на робочому столі ви можете розраховувати принаймні на основну підтримку Map. Не на мобільних пристроях. Наприклад, браузер Android взагалі не підтримує Map. Навіть на робочому столі деякі реалізації є неповними. Наприклад, IE11 досі не підтримує перерахування через "для ... з ...", тому якщо ви хочете сумісності з IE, вам доведеться використовувати огидний .forEach хитрість. Також JSON.stringify () не працює для Map у жодному браузері, який я пробував. Також ініціалізатори не працюють в IE або Safari.
Дейв Бертон

3
Є чудова підтримка браузера. Перевірте ще раз. У будь-якому випадку, це досить просто для заповнення, тому підтримка рідного браузера - це не проблема.
Бред

132

Якщо у вас немає конкретних причин цього не робити, просто використовуйте звичайний об’єкт. На властивості об’єктів у Javascript можна посилатися за допомогою синтаксису стилю хештеля:

var hashtable = {};
hashtable.foo = "bar";
hashtable['bar'] = "foo";

Обидва fooі barелементи тепер можуть потім посилатися як:

hashtable['foo'];
hashtable['bar'];
// or
hashtable.foo;
hashtable.bar;

Звичайно, це означає, що ваші клавіші повинні бути рядками. Якщо вони не є рядками, вони внутрішньо перетворюються на рядки, тому це все ще може працювати, YMMV.


1
Ключі як цілі числа не викликали у мене жодної проблеми. stackoverflow.com/questions/2380019 / ...
Jónás Elfström

10
Jonas: майте на увазі, що ваші цілі числа перетворюються на рядки під час встановлення властивості: var hash = {}; hash[1] = "foo"; alert(hash["1"]);попереджає "foo".
Тім Даун

17
Що робити, якщо одним із ваших ключів є " прото " або " батьківський "?
Будь ласка, продовжуйте

5
Зауважте, що Об'єкти не можна використовувати як ключі в JavaScript. Добре, вони можуть, але вони перетворюються на свої рядкові представлення, тож будь-який Об'єкт закінчується як точно той самий ключ. Див. Пропозицію jshashtable @ TimDown нижче.
ericsoco

21
Цей приклад заплутаний, оскільки ви використовуєте foo і bar як ключ і значення у двох випадках. Набагато чіткіше показати, що var dict = {}; dict.key1 = "val1"; dict["key2"] = "val2";елемент1 d1 класу dict може посилатися еквівалентно і обома, dict["key1"]і dict.key1.
Джим

49

Оскільки кожен об’єкт в JS поводиться як - і, як правило, реалізується як - хештеб, я просто йду з цим ...

var hashSweetHashTable = {};

26
Оновлений, тому що він не показує, як насправді отримати доступ до значень у "хештелі".
IQAndreas

Мені запізнюється на 9 років (я не знав багато нічого про програмування, не кажучи вже про цей сайт тоді), але ... Що робити, якщо ви намагаєтесь зберігати точки на карті, і вам потрібно перевірити, чи щось вже є у точці на карті? У цьому випадку вам найкраще використовувати для цього HashTable, шукаючи координати ( об’єкт , а не рядок ).
Майк Уоррен

@MikeWarren if (hashSweetHashTable.foo)повинен ввести блок if, якщо fooвстановлено.
Корай Тугай

21

тому в C # код виглядає так:

Dictionary<string,int> dictionary = new Dictionary<string,int>();
dictionary.add("sample1", 1);
dictionary.add("sample2", 2);

або

var dictionary = new Dictionary<string, int> {
    {"sample1", 1},
    {"sample2", 2}
};

в JavaScript

var dictionary = {
    "sample1": 1,
    "sample2": 2
}

Об'єкт словника C # містить корисні методи, наприклад, dictionary.ContainsKey() у JavaScript ми могли б використовувати hasOwnPropertyподібні

if (dictionary.hasOwnProperty("sample1"))
    console.log("sample1 key found and its value is"+ dictionary["sample1"]);

1
Оновлення мені не потрібно писати відповіді проhasOwnProperty
бричін

18

Якщо вам потрібно, щоб ваші ключі були будь-яким об'єктом, а не просто рядками, ви можете використовувати мій jshashtable .


3
Скільки годин я провів спотикаючись про те, що Об'єкти насправді не можна використовувати як ключі для масивів JS-style-Object-as-asotivetive, перш ніж я це знайшов? Дякую, Тіме.
ericsoco

1
Словник Flash / AS3, як і більшість інших мов, підтримують посилання на об'єкти як ключі. JavaScript все ще не реалізував його, але я думаю, що це буде в майбутньому специфікацією як якийсь клас Map. Знову з поліфілами тим часом; стільки для стандартів. О, зачекайте ... нарешті, у 2015 році карта, схоже, приїхала: stackoverflow.com/a/30088129/88409 і підтримується "сучасними" браузерами, lol: kangax.github.io/compat-table/es6/# Карта (і не дуже широко підтримується). Лише на десятиліття відстає від AS3.
Трайнко

Тіме, можливо, вам слід оновити jshashtable, щоб використовувати Map () там, де це можливо.
Дейв Бертон

1
@DaveBurton: Хороший план. Я зроблю це, як тільки у мене буде час.
Тім Даун

6
function HashTable() {
    this.length = 0;
    this.items = new Array();
    for (var i = 0; i < arguments.length; i += 2) {
        if (typeof (arguments[i + 1]) != 'undefined') {
            this.items[arguments[i]] = arguments[i + 1];
            this.length++;
        }
    }

    this.removeItem = function (in_key) {
        var tmp_previous;
        if (typeof (this.items[in_key]) != 'undefined') {
            this.length--;
            var tmp_previous = this.items[in_key];
            delete this.items[in_key];
        }

        return tmp_previous;
    }

    this.getItem = function (in_key) {
        return this.items[in_key];
    }

    this.setItem = function (in_key, in_value) {
        var tmp_previous;
        if (typeof (in_value) != 'undefined') {
            if (typeof (this.items[in_key]) == 'undefined') {
                this.length++;
            } else {
                tmp_previous = this.items[in_key];
            }

            this.items[in_key] = in_value;
        }

        return tmp_previous;
    }

    this.hasItem = function (in_key) {
        return typeof (this.items[in_key]) != 'undefined';
    }

    this.clear = function () {
        for (var i in this.items) {
            delete this.items[i];
        }

        this.length = 0;
    }
}

1
Людям, які голосують за це, можете прокоментувати, чому? Ця відповідь була розміщена в 2011 році, а не в поточну дату.
Бірей

2
Я не проголосував, але ... ви не повинні використовувати масив як об'єкт. Не на 100% впевнений, чи це був ваш намір. Використовуйте фрагмент на масивах not delete для повторного індексування; видалити нормально, але буде встановлено невизначено - краще бути явним; use = undefined on the object too b / c це швидше (але більше пам’яті). Коротше кажучи: завжди використовуйте об’єкт: {}не масив: []або new Array()якщо ви маєте намір мати рядкові клавіші, інакше у двигуна js виникнуть проблеми - він побачить 2 типи для 1 змінної, що означає відсутність оптимізації, або він запуститься з масивом і реалізує він повинен змінити об'єкт (можливий перерозподіл).
Graeme Wicksted

2
Так само, як і у відповіді Алекса Хокінса, будь ласка, поясніть, чому цей досить складний кодовий код насправді корисний і кращий, ніж інші короткі відповіді, наведені тут.
Томас Темпельман

6

Я створив це для досягнення певної проблеми, наприклад, відображення об’єктних ключів, можливість перерахування ( forEach()методом) та очищення.

function Hashtable() {
    this._map = new Map();
    this._indexes = new Map();
    this._keys = [];
    this._values = [];
    this.put = function(key, value) {
        var newKey = !this.containsKey(key);
        this._map.set(key, value);
        if (newKey) {
            this._indexes.set(key, this.length);
            this._keys.push(key);
            this._values.push(value);
        }
    };
    this.remove = function(key) {
        if (!this.containsKey(key))
            return;
        this._map.delete(key);
        var index = this._indexes.get(key);
        this._indexes.delete(key);
        this._keys.splice(index, 1);
        this._values.splice(index, 1);
    };
    this.indexOfKey = function(key) {
        return this._indexes.get(key);
    };
    this.indexOfValue = function(value) {
        return this._values.indexOf(value) != -1;
    };
    this.get = function(key) {
        return this._map.get(key);
    };
    this.entryAt = function(index) {
        var item = {};
        Object.defineProperty(item, "key", {
            value: this.keys[index],
            writable: false
        });
        Object.defineProperty(item, "value", {
            value: this.values[index],
            writable: false
        });
        return item;
    };
    this.clear = function() {
        var length = this.length;
        for (var i = 0; i < length; i++) {
            var key = this.keys[i];
            this._map.delete(key);
            this._indexes.delete(key);
        }
        this._keys.splice(0, length);
    };
    this.containsKey = function(key) {
        return this._map.has(key);
    };
    this.containsValue = function(value) {
        return this._values.indexOf(value) != -1;
    };
    this.forEach = function(iterator) {
        for (var i = 0; i < this.length; i++)
            iterator(this.keys[i], this.values[i], i);
    };
    Object.defineProperty(this, "length", {
        get: function() {
            return this._keys.length;
        }
    });
    Object.defineProperty(this, "keys", {
        get: function() {
            return this._keys;
        }
    });
    Object.defineProperty(this, "values", {
        get: function() {
            return this._values;
        }
    });
    Object.defineProperty(this, "entries", {
        get: function() {
            var entries = new Array(this.length);
            for (var i = 0; i < entries.length; i++)
                entries[i] = this.entryAt(i);
            return entries;
        }
    });
}


Документація класу Hashtable

Методи:

  • get(key)
    Повертає значення, пов'язане з вказаним ключем.
    Параметри::
    key ключ, з якого витягують значення.

  • put(key, value)
    Пов’язує вказане значення із вказаним ключем.
    Параметри::
    key ключ, до якого пов'язують значення.
    value: Значення для асоціації з ключем.

  • remove(key)
    Вилучає вказаний ключ зі своїм значенням.
    Параметри::
    key ключ для видалення.

  • clear()
    Очищає всі хештелі, видаляючи і ключі, і значення.

  • indexOfKey(key)
    Повертає індекс вказаного ключа, виходячи з порядку додання.
    Параметри::
    key ключ, який отримує індекс.

  • indexOfValue(value)
    Повертає індекс зазначеного значення, виходячи з порядку додання.
    Параметри:
    value Значення яких отримує індекс.
    Примітки:
    Ця інформація indexOf()отримується методом масиву, тому вона порівнює об'єкт саме з toString()методом.

  • entryAt(index)
    Повертає об'єкт з двома властивостями: ключ і значення, що представляють запис у вказаному індексі.
    Параметри::
    index Індекс запису, який потрібно отримати.

  • containsKey(key)
    Повертає, чи містить хеш-файл вказаний ключ.
    Параметри::
    key ключ для перевірки.

  • containsValue(value)
    Повертає, чи містить хеш-файл вказане значення.
    Параметри::
    value Значення для перевірки.

  • forEach(iterator)
    Ітераціює всі записи у вказаному iterator.
    Параметри:
    value Спосіб А з 3 - ма параметрами: key, valueіindex , де indexє індекс записи.

    Властивості:

  • length ( Лише для читання )
    Отримує кількість записів у хештелі.

  • keys ( Лише для читання )
    Отримує масив усіх клавіш у хештелі.

  • values ( Лише для читання )
    Отримує масив усіх значень у хештелі.

  • entries ( Лише для читання )
    Отримує масив усіх записів у хештелі. Вони представлені в тій же формі методу entryAt().


2

https://gist.github.com/alexhawkins/f6329420f40e5cafa0a4

var HashTable = function() {
  this._storage = [];
  this._count = 0;
  this._limit = 8;
}


HashTable.prototype.insert = function(key, value) {
  //create an index for our storage location by passing it through our hashing function
  var index = this.hashFunc(key, this._limit);
  //retrieve the bucket at this particular index in our storage, if one exists
  //[[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ]  [ [k,v] ] ]
  var bucket = this._storage[index]
    //does a bucket exist or do we get undefined when trying to retrieve said index?
  if (!bucket) {
    //create the bucket
    var bucket = [];
    //insert the bucket into our hashTable
    this._storage[index] = bucket;
  }

  var override = false;
  //now iterate through our bucket to see if there are any conflicting
  //key value pairs within our bucket. If there are any, override them.
  for (var i = 0; i < bucket.length; i++) {
    var tuple = bucket[i];
    if (tuple[0] === key) {
      //overide value stored at this key
      tuple[1] = value;
      override = true;
    }
  }

  if (!override) {
    //create a new tuple in our bucket
    //note that this could either be the new empty bucket we created above
    //or a bucket with other tupules with keys that are different than 
    //the key of the tuple we are inserting. These tupules are in the same
    //bucket because their keys all equate to the same numeric index when
    //passing through our hash function.
    bucket.push([key, value]);
    this._count++
      //now that we've added our new key/val pair to our storage
      //let's check to see if we need to resize our storage
      if (this._count > this._limit * 0.75) {
        this.resize(this._limit * 2);
      }
  }
  return this;
};


HashTable.prototype.remove = function(key) {
  var index = this.hashFunc(key, this._limit);
  var bucket = this._storage[index];
  if (!bucket) {
    return null;
  }
  //iterate over the bucket
  for (var i = 0; i < bucket.length; i++) {
    var tuple = bucket[i];
    //check to see if key is inside bucket
    if (tuple[0] === key) {
      //if it is, get rid of this tuple
      bucket.splice(i, 1);
      this._count--;
      if (this._count < this._limit * 0.25) {
        this._resize(this._limit / 2);
      }
      return tuple[1];
    }
  }
};



HashTable.prototype.retrieve = function(key) {
  var index = this.hashFunc(key, this._limit);
  var bucket = this._storage[index];

  if (!bucket) {
    return null;
  }

  for (var i = 0; i < bucket.length; i++) {
    var tuple = bucket[i];
    if (tuple[0] === key) {
      return tuple[1];
    }
  }

  return null;
};


HashTable.prototype.hashFunc = function(str, max) {
  var hash = 0;
  for (var i = 0; i < str.length; i++) {
    var letter = str[i];
    hash = (hash << 5) + letter.charCodeAt(0);
    hash = (hash & hash) % max;
  }
  return hash;
};


HashTable.prototype.resize = function(newLimit) {
  var oldStorage = this._storage;

  this._limit = newLimit;
  this._count = 0;
  this._storage = [];

  oldStorage.forEach(function(bucket) {
    if (!bucket) {
      return;
    }
    for (var i = 0; i < bucket.length; i++) {
      var tuple = bucket[i];
      this.insert(tuple[0], tuple[1]);
    }
  }.bind(this));
};


HashTable.prototype.retrieveAll = function() {
  console.log(this._storage);
  //console.log(this._limit);
};

/******************************TESTS*******************************/

var hashT = new HashTable();

hashT.insert('Alex Hawkins', '510-599-1930');
//hashT.retrieve();
//[ , , , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Boo Radley', '520-589-1970');
//hashT.retrieve();
//[ , [ [ 'Boo Radley', '520-589-1970' ] ], , [ [ 'Alex Hawkins', '510-599-1930' ] ] ]
hashT.insert('Vance Carter', '120-589-1970').insert('Rick Mires', '520-589-1970').insert('Tom Bradey', '520-589-1970').insert('Biff Tanin', '520-589-1970');
//hashT.retrieveAll();
/* 
[ ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Tom Bradey', '520-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Rick Mires', '520-589-1970' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '520-589-1970' ] ] ]
*/

//overide example (Phone Number Change)
//
hashT.insert('Rick Mires', '650-589-1970').insert('Tom Bradey', '818-589-1970').insert('Biff Tanin', '987-589-1970');
//hashT.retrieveAll();

/* 
[ ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Tom Bradey', '818-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Rick Mires', '650-589-1970' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]

*/

hashT.remove('Rick Mires');
hashT.remove('Tom Bradey');
//hashT.retrieveAll();

/* 
[ ,
  [ [ 'Boo Radley', '520-589-1970' ] ],
  ,
  [ [ 'Alex Hawkins', '510-599-1930' ] ],
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]


*/

hashT.insert('Dick Mires', '650-589-1970').insert('Lam James', '818-589-1970').insert('Ricky Ticky Tavi', '987-589-1970');
hashT.retrieveAll();


/* NOTICE HOW HASH TABLE HAS NOW DOUBLED IN SIZE UPON REACHING 75% CAPACITY ie 6/8. It is now size 16.
 [,
  ,
  [ [ 'Vance Carter', '120-589-1970' ] ],
  [ [ 'Alex Hawkins', '510-599-1930' ],
    [ 'Dick Mires', '650-589-1970' ],
    [ 'Lam James', '818-589-1970' ] ],
  ,
  ,
  ,
  ,
  ,
  [ [ 'Boo Radley', '520-589-1970' ],
    [ 'Ricky Ticky Tavi', '987-589-1970' ] ],
  ,
  ,
  ,
  ,
  [ [ 'Biff Tanin', '987-589-1970' ] ] ]




*/
console.log(hashT.retrieve('Lam James'));  //818-589-1970
console.log(hashT.retrieve('Dick Mires')); //650-589-1970
console.log(hashT.retrieve('Ricky Ticky Tavi')); //987-589-1970
console.log(hashT.retrieve('Alex Hawkins')); //510-599-1930
console.log(hashT.retrieve('Lebron James')); //null

3
Виглядає добре. Тепер, будь ласка, поясніть, ЧОМУ це корисно і може бути краще, ніж усі інші відповіді тут.
Томас Темпельман

1

Ви можете створити його за допомогою наступного:

var dictionary = { Name:"Some Programmer", Age:24, Job:"Writing Programs"  };

//Iterate Over using keys
for (var key in dictionary) {
  console.log("Key: " + key + " , " + "Value: "+ dictionary[key]);
}

//access a key using object notation:
console.log("Her Name is: " + dictionary.Name)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.