[javascript] 자바 스크립트 해시 맵

이 답변의 업데이트 3에서 분명한 것처럼 이 표기법은 다음과 같습니다.

var hash = {};
hash[X]

실제로 객체를 해시하지 않습니다 X. 실제로 X는 문자열 인 .toString()경우 ( 객체 인 경우 또는 다양한 기본 유형에 대한 다른 내장 변환을 통해) ” hash” 에서 해시하지 않고 해당 문자열을 찾습니다 . 두 개의 서로 다른 객체가 동일한 문자열 변환을 갖는 경우 객체 동등성이 검사되지 않습니다.

이것을 감안할 때-자바 스크립트에서 효율적인 해시 맵 구현이 있습니까? (예를 들어, 구글의 두 번째 결과는 javascript hashmap모든 연산에 대해 O (n) 구현을 생성합니다. 다른 여러 결과는 동일한 문자열 표현을 가진 서로 다른 객체가 서로 덮어 쓴다는 사실을 무시합니다.



답변

왜 객체를 수동으로 해시하고 결과 문자열을 일반 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는 인코딩 , 또는 DojoX는 UUID . 후자의 솔루션은 고유 한 키를 생성 할 수 있지만 개인적으로, 특히 객체를 고유하게 만드는 것이 무엇인지 아는 경우 모든 비용으로 키를 피하려고합니다.

2014 년 업데이트 : 2008 년에 답한이 간단한 솔루션에는 여전히 더 많은 설명이 필요합니다. Q & A 양식으로 아이디어를 명확히하겠습니다.

귀하의 솔루션에는 실제 해시가 없습니다. 어디입니까 ???

JavaScript는 고급 언어입니다. 기본 프리미티브 ( Object )에는 속성을 유지하기위한 해시 테이블이 포함되어 있습니다. 이 해시 테이블은 일반적으로 효율성을 위해 저수준 언어로 작성됩니다. 문자열 키와 함께 간단한 객체를 사용하여 우리는 노력하지 않고 효율적으로 구현 된 해시 테이블을 사용합니다.

그들이 해시를 사용한다는 것을 어떻게 알 수 있습니까?

키로 객체 컬렉션을 처리 할 수있는 주요 방법에는 세 가지가 있습니다.

  • 순서가 없습니다. 이 경우 키로 객체를 검색하려면 찾을 때 멈추는 모든 키를 거쳐야합니다. 평균적으로 n / 2 비교가 필요합니다.
  • 주문했다.
    • 예제 # 1 : 정렬 된 배열-이진 검색을 수행하면 평균 ~ log2 (n) 비교 후 키를 찾을 수 있습니다. 훨씬 낫다.
    • 예 # 2 : 나무. 다시 ~ log (n) 시도가됩니다.
  • 해시 테이블. 평균적으로 일정한 시간이 필요합니다. 비교 : O (n) vs. O (log n) vs. O (1). 팔.

분명히 JavaScript 객체는 일반적인 경우를 처리하기 위해 어떤 형태의 해시 테이블을 사용합니다.

브라우저 공급 업체는 실제로 해시 테이블을 사용합니까 ???

정말.

충돌을 처리합니까?

예. 위 참조. 동일하지 않은 문자열에서 충돌이 발견되면 주저하지 말고 공급 업체에 버그를 신고하십시오.

그래서 당신의 생각은 무엇입니까?

객체를 해시하려면 객체를 고유하게 만들고 키로 사용하십시오. 실제 해시를 계산하거나 해시 테이블을 에뮬레이션하지 마십시오. 기본 JavaScript 객체에서 이미 효율적으로 처리됩니다.

Object기본 속성과의 충돌 가능성을 피하면서 내장 된 해시 테이블을 활용 하려면 JavaScript와 함께이 키를 사용하십시오 .

시작하는 예 :

  • 객체에 고유 한 사용자 이름이 포함되어 있으면 키로 사용하십시오.
  • 고유 한 고객 번호가 포함 된 경우이를 키로 사용하십시오.
    • SSN 또는 여권 번호와 같은 정부에서 발행 한 고유 번호가 포함되어 있고 시스템에서 중복을 허용하지 않는 경우 키로 사용하십시오.
  • 필드 조합이 고유 한 경우이를 키로 사용하십시오.
    • 국가 약어 + 운전 면허 번호는 훌륭한 열쇠입니다.
    • 국가 약어 + 여권 번호도 훌륭한 열쇠입니다.
  • 필드 또는 전체 객체의 일부 함수는 고유 한 값을 반환 할 수 있습니다. 키로 사용하십시오.

귀하의 제안을 사용하고 사용자 이름을 사용하여 모든 객체를 캐시했습니다. 그러나 일부 현명한 사람은 이름이 “toString”이며 내장 속성입니다! 지금 어떻게해야합니까?

결과 키가 독점적으로 라틴 문자로만 구성되는 것이 원격으로 가능하다면, 그것에 대해 무언가를해야합니다. 예를 들어, 시작 또는 끝에 원하는 비 라틴 유니 코드 문자를 추가하여 기본 특성 “#toString”, “#MarySmith”와 충돌하지 않도록하십시오. 복합 키를 사용하는 경우, 라틴어가 아닌 분리 문자를 사용하여 키 구성 요소를 분리하십시오 : “name, city, state”.

일반적으로 이곳은 창의력을 발휘해야하는 곳으로 주어진 제한 사항 (고유성, 기본 속성과의 잠재적 충돌)이있는 가장 쉬운 키를 선택합니다.

참고 : 고유 키는 정의에 따라 충돌하지 않지만 잠재적 해시 충돌은 기본에 의해 처리됩니다 Object.

산업용 솔루션이 마음에 들지 않는 이유는 무엇입니까?

최고의 코드 인 IMHO는 코드가 전혀 없습니다. 오류가없고 유지 보수가 필요 없으며 이해하기 쉽고 즉각적으로 실행됩니다. 내가 본 “JavaScript의 해시 테이블”은 100 줄 이상의 코드였으며 여러 객체를 포함했습니다. 다음과 비교하십시오 : dict[key] = value.

또 다른 요점 : JavaScript와 동일한 기본 객체를 사용하여 이미 구현 된 것을 구현하여 저수준 언어로 작성된 기본 객체의 성능을 능가 할 수 있습니까?

여전히 키없이 객체를 해시하고 싶습니다!

운이 좋았습니다 : ECMAScript 6 (2015 년 중반 릴리스 예정이며, 그 후 널리 보급되기까지 1 ~ 2 년이 소요됨) map
set을 정의합니다
.

정의에 따라 판단하면 객체 주소를 키로 사용할 수 있으므로 인공 키없이 객체를 즉시 구별 할 수 있습니다. 두 개의 서로 다르지만 동일한 객체 인 OTOH는 별개의 것으로 매핑됩니다.

MDN 과의 비교 분석 :

객체는지도와 유사하여 키를 값으로 설정하고, 해당 값을 검색하고, 키를 삭제하고, 키에 무언가가 저장되어 있는지 감지 할 수 있습니다. 이 때문에 (및 내장 된 대안이 없기 때문에) 객체는 역사적으로지도로 사용되었습니다. 그러나 어떤 경우에는 맵 사용을 선호하는 중요한 차이점이 있습니다.

  • 객체의 키는 문자열과 심볼이지만 함수, 객체 및 기본 요소를 포함하여지도의 모든 값이 될 수 있습니다.
  • 맵에있는 키는 객체에 추가 된 키가 아닌 순서로 정렬됩니다. 따라서 반복 할 때 Map 객체는 삽입 순서대로 키를 반환합니다.
  • size 속성을 사용하여 Map의 크기를 쉽게 얻을 수 있지만 Object의 속성 수는 수동으로 결정해야합니다.
  • 맵은 반복 가능하므로 직접 반복 할 수 있지만 객체를 반복하려면 키를 가져 와서 반복해야합니다.
  • 객체에는 프로토 타입이 있으므로, 조심하지 않으면 키와 충돌 할 수있는 기본 키가 맵에 있습니다. ES5부터는 map = Object.create (null)을 사용하여 무시할 수 있지만 거의 수행되지 않습니다.
  • 키 쌍을 자주 추가 및 제거하는 시나리오에서 맵 성능이 향상 될 수 있습니다.

답변

문제 설명

JavaScript에는 임의의 키로 임의의 값에 액세스 할 수있는 내장 된 일반 유형 ( 연관 배열 또는 사전 이라고도 함 )이 없습니다. JavaScript의 기본 데이터 구조는 객체로 , 문자열을 키로 만 받아들이고 프로토 타입 상속, getter 및 setter 및 추가 부두와 같은 특수 의미를 갖는 특수 유형의 맵입니다.

객체를 맵으로 사용하는 경우 키가를 통해 문자열 값으로 변환 toString()되어 매핑 5'5'동일한 값으로 변환되고 toString()색인이 생성 된 값으로 메서드를 덮어 쓰지 않는 모든 객체가 생성됩니다 '[object Object]'. 확인하지 않으면 상속 된 속성에 무의식적으로 액세스 할 수도 있습니다 hasOwnProperty().

JavaScript의 내장 배열 유형은 1 비트에 도움이되지 않습니다. JavaScript 배열은 연관 배열이 아니라 몇 가지 특별한 속성을 가진 객체 일뿐입니다. 지도로 사용할 수없는 이유를 알고 싶으면 여기를 참조하십시오 .

유진의 솔루션

Eugene Lazutkin은 이미 커스텀 해시 함수를 사용하여 딕셔너리 객체의 속성으로 연관된 값을 찾는 데 사용할 수있는 고유 한 문자열을 생성하는 기본 아이디어를 설명했습니다. 객체는 내부적으로 해시 테이블 로 구현되므로 가장 빠른 솔루션 일 것 입니다.

  • 참고 : 해시 테이블 (때로는 해시 맵 이라고도 함 )은 백업 배열 및 숫자 해시 값을 통한 조회를 사용하여 맵 개념의 특정 구현입니다. 런타임 환경은 다른 구조 (예 : 검색 트리 또는 건너 뛰기 목록 )를 사용하여 JavaScript 오브젝트를 구현할 수 있지만 오브젝트가 기본 데이터 구조이므로 충분히 최적화되어야합니다.

임의의 개체에 대해 고유 한 해시 값을 얻으려면 전역 카운터를 사용하고 개체 자체 (예 : 속성 __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 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 기능 은 here에서 얻을 수 있습니다 .


답변

나는이 질문이 꽤 오래되었다는 것을 알고 있지만 요즘에는 외부 라이브러리를 갖춘 훌륭한 솔루션이 있습니다.

JavaScript에는 언어도 제공 Map됩니다.


답변

다음은 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'] );    // fives the value of map_value_1

......  etc  .....


답변

ECMAScript 2015 (ES6)에 따르면 표준 자바 스크립트에는 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"


답변

ES6 WeakMap또는 Map다음을 사용할 수 있습니다 .

  • WeakMaps는 키가 객체 인 키 / 값 맵입니다.

  • Map객체는 간단한 키 / 값 맵입니다. 임의의 값 (객체 및 프리미티브 값 모두)은 키 또는 값으로 사용될 수 있습니다.

두 가지 모두 광범위하게 지원되지는 않지만 ES6 Shim (기본 ES5 또는 ES5 Shim 필요 )을 사용하여 지원할 수 Map는 있지만 지원 하지는 않습니다 WeakMap( 이유 참조 ).


답변

객체 / 값 쌍의 내부 상태 쌍에 저장해야합니다.

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를 첨부하는 것입니다. 내가 가장 좋아하는 방법 중 하나는 Object 수퍼 클래스에서 상속 된 내장 메소드 중 하나를 사용하여 사용자 정의 함수 passthrough로 바꾸고 해당 함수 오브젝트에 특성을 첨부하는 것입니다. 이 작업을 수행하기 위해 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];
}

이 버전은 약간 더 빠르지 만 이론 상으로는 큰 데이터 세트의 경우 훨씬 더 빠릅니다.