Archive

Posts Tagged ‘hashmap javascript’

Simple HashMap

July 9th, 2009 Daniello No comments

I’ve implemented HashMapJS, a simple hash map like implementation.
The project is located at github.

Here is the code with the usage examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
=====================================================================
   @license MIT
   @author Daniel Kwiecinski <daniel.kwiecinski@lambder.com>
   @copyright 2009 Daniel Kwiecinski.
   @end
=====================================================================
*/
var HashMap = function() {
  this.initialize();
}
 
HashMap.prototype = {
  hashkey_prefix: "<#HashMapHashkeyPerfix>",
  hashcode_field: "<#HashMapHashcodeField>",
  hashmap_instance_id: 0,
 
  initialize: function() {
    this.backing_hash = {};
    this.code = 0;
    this.hashmap_instance_id += 1;
    this.instance_id = this.hashmap_instance_id;
  },
 
  hashcodeField: function() {
    return this.hashcode_field + this.instance_id;
  },
  /*
   maps value to key returning previous assocciation
   */
  put: function(key, value) {
    var prev;
 
    if (key && value) {
      var hashCode;
      if (typeof(key) === "number" || typeof(key) === "string") {
        hashCode = key;
      } else {
        hashCode = key[this.hashcodeField()];
      }
      if (hashCode) {
        prev = this.backing_hash[hashCode];
      } else {
        this.code += 1;
        hashCode = this.hashkey_prefix + this.code;
        key[this.hashcodeField()] = hashCode;
      }
      this.backing_hash[hashCode] = value;
    }
    return prev;
  },
  /*
   returns value associated with given key
   */
  get: function(key) {
    var value;
    if (key) {
      var hashCode;
      if (typeof(key) === "number" || typeof(key) === "string") {
        hashCode = key;
      } else {
        hashCode = key[this.hashcodeField()];
      }
      if (hashCode) {
        value = this.backing_hash[hashCode];
      }
    }
    return value;
  },
  /*
   deletes association by given key.
   Returns true if the assocciation existed, false otherwise
   */
  del: function(key) {
    var success = false;
    if (key) {
      var hashCode;
      if (typeof(key) === "number" || typeof(key) === "string") {
        hashCode = key;
      } else {
        hashCode = key[this.hashcodeField()];
      }
      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")
}
 
 
// primitive types keys 
var d_value = {struct: "structD"};
my_map.put(1, d_value);
 
if (my_map.get(1) !== d_value) {
  throw("fail7")
}

The keys and values can be arbitrary javascript objects.
There is no requirements on objects used as keys or values.

The mechanism is trivial. For every key there is generated unique id (per HashMap instance).
That id is injected to the key object under high unlikely to collide field name ;)

That id is then used to keying in the underlying baking standard javascript association object.

Notes:
1. This HashMap implementation is not thread safe.
OK I know javascript is not multithreaded but it can have concurrent activities (e.g. setTimeout)
This is enough to have race conditions.

2. The mentioned high unlikely to collide name is not a problem as log as there is no
massively concurrent activities.

Roadmap:
1. Make the HashMap implementation thread safe by applying The Wallace Variation of Lamport’s bakery algorithm.
http://www.polyglotinc.com/AJAXscratch/Mutex/mutualExclusion.html

Bon Appétit,
Daniel Kwiecinski

Categories: programming Tags: