Skip to content

Commit

Permalink
Hash table on top of native Array
Browse files Browse the repository at this point in the history
  • Loading branch information
felipernb authored and Felipe Ribeiro committed Jun 4, 2014
1 parent ba40d49 commit 45e67e0
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 1 deletion.
120 changes: 120 additions & 0 deletions data_structures/hash_table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* Copyright (C) 2014 Felipe Ribeiro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
'use strict';

var LinkedList = require('./linked_list');

function HashTable(initialCapacity) {
this._table = new Array(initialCapacity || 64);
this._items = 0;

Object.defineProperty(this, 'capacity', {
get: function () {
return this._table.length;
}
});
}

/**
* (Same algorithm as Java's String.hashCode)
* Returns a hash code for this string. The hash code for a String object is
* computed as: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* using int arithmetic, where s[i] is the ith character of the string,
* n is the length of the string, and ^ indicates exponentiation.
* (The hash value of the empty string is zero.)
*/
HashTable.prototype.hash = function (s) {
var hash = 0;
for (var i = 0; i < s.length; i++) {
hash = ((hash << 5) - hash) + s.charCodeAt(i);
hash &= hash; // Keep it a 32bit int
}
return hash;
};

HashTable.prototype.get = function (key) {
var i = this._position(key);
var node;
if ((node = this._findInList(this._table[i], key))) {
return node.value.v;
}
};

HashTable.prototype.put = function (key, value) {
var i = this._position(key);
if (!this._table[i]) {
// Hashing with chaining
this._table[i] = new LinkedList();
}
var item = {k: key, v: value};

var node = this._findInList(this._table[i], key);
if (node) {
// if the key already exists in the list, replace
// by the current item
node.value = item;
} else {
this._table[i].add(item);
this._items++;

if (this._items === this.capacity) this._increaseCapacity();
}
};

HashTable.prototype.del = function (key) {
var i = this._position(key);
var node;

if ((node = this._findInList(this._table[i], key))) {

this._table[i].delNode(node);
this._items--;
}
};

HashTable.prototype._position = function (key) {
return this.hash(key) % this.capacity;
};

HashTable.prototype._findInList = function (list, key) {
var node = list && list.head;
while (node) {
if (node.value.k === key) return node;
node = node.next;
}
};

HashTable.prototype._increaseCapacity = function () {
var oldTable = this._table;
this._table = new Array(2 * this.capacity);
this._items = 0;

for (var i = 0; i < oldTable.length; i++) {
var node = oldTable[i] && oldTable[i].head;
while (node) {
this.put(node.value.k, node.value.v);
node = node.next;
}
}
};

module.exports = HashTable;
5 changes: 4 additions & 1 deletion data_structures/linked_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,11 @@ LinkedList.prototype.del = function (index) {
if (index >= this.length || index < 0) {
throw new Error('Index out of bounds');
}
var node = this.getNode(index);

this.delNode(this.getNode(index));
};

LinkedList.prototype.delNode = function (node) {
if (node === this.tail) {
// node is the last element
this.tail = node.prev;
Expand Down
133 changes: 133 additions & 0 deletions test/data_structures/hash_table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* Copyright (C) 2014 Felipe Ribeiro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
'use strict';

var HashTable = require('../../data_structures/hash_table'),
assert = require('assert');

describe('Hash Table', function () {
it('should calculate hashes using the same algorithm as '+
'Java\'s String.hashCode', function () {
var h = new HashTable();
assert.equal(h.hash('The quick brown fox jumps over the lazy dog'),
-609428141);
assert.equal(h.hash('Testing the hashCode function'), 1538083358);
assert.equal(h.hash(''), 0);
assert.equal(h.hash('a'), 97);
var longString =
'k"hg3q#+~/l2Eljan;DB x.P<pX[!C`/Nr6w~YIPz;X3z<]b6nDvda|ToZM+a%D#' +
':PE@z[bl$/PRT7m76}FV=UW/3SPkkdRkuC~9TKgMg.^#n)iiq{AZ?}+pv;>%-:iA' +
'/b/hG($8-SZcZX871&;fDEWthw.b5agzov],X00--O:mcQ$JFi-4uIo"D:(r(yvs' +
'dj%Pq/b$sY5(O8!{^icIBwTT>,fv=$@d<tqde$]?X##Gb![s44rA=fSI1o#S;0V[' +
'rPy#=z7Vf0"Si!D8oN5;qNiXNA_)(9*0JD<r$uY~LijTd@dtA+N-GK5yC80WRiJ{' +
'@7x:WbD/$k6Db~,/aU7n)9?{cP4z6>D(>167.xqSDXjBS#TV3oIjMGCo9)!e&hO ' +
'I<[jlz3]r-FFFeNe#Ch4oQ,4A;i,3&3Oq*2LW(KFUW9b$}"z8,B>HRH9.D%.S~o3' +
'L_6{wu!Kp538AmLREp*ZP`]K9}uRGEEUj37[PQq2Y>cf_{L={Ko"ADnZ8d[q0{-3' +
'@=e8UC4y)@aCefzleW[>Q8y}@Of9{WNI|?ShSF7C{<JYRb6QBrNauQzUio(]XIsk' +
's`*F9tK|GBhaj.W%XV~7zSx}tFBI6h}|a sah/$-|fJs_;Ci5q-_d]+-0o/vY|:6' +
'~cx%aJx8sy*G!"wVL-S?g.BPSB`N +QPCvTar`[= WYsnxR2fmE2}ON]8C:*g}*V' +
't%Bh1D`,s`>62(A4o<g9G2d+)R;;p`?wUdr^uy:ibCX)qmo.xH/rf"xmC-p($akh' +
'gVM;#K i )m%I8(0qFCHbzr;gVAvj/jrae=0DgF3C?&9Rvs$&J"r`yvrhM^A=?Wi' +
'fE$O[YV8X3PV6TR(*Ed4|y[8tG~K=[MxgLI%yx]16Kg3YSHE{2^1TOAnf`EsKWm,' +
'WGD)s;Zs<8K6(K_kVko"mV82Pcl)0Rx}jfq3VBm:MOX/gLfSPvLx~%(3jHh5gG2e' +
'JaQ|nW}ekR_W5Ldv`@j^hd%Wiw6moGekrS>k7gRR|dd?7Pi:`0; r_wq=-F-e(iY';
assert.equal(h.hash(longString), -998071508);
});

it('should initialize the table with the given capacity',
function () {
var h = new HashTable();
assert.equal(h.capacity, 64); // default initial capacity;

h = new HashTable(2);
assert.equal(h.capacity, 2);
});

it('should allow putting and getting elements from the table', function () {
var h = new HashTable(16);
var a = {a: 'foo', b: 'bar'};
h.put('foo', a);
assert.strictEqual(h.get('foo'), a);

var b = {a: 'bar', b: 'baz'};
h.put('bar', b);
assert.strictEqual(h.get('bar'), b);
});

it('should replace items if the same key is reused', function () {
var h = new HashTable(16);
var a = {a: 'foo', b: 'bar'};
h.put('foo', a);
assert.strictEqual(h.get('foo'), a);

var b = {a: 'bar', b: 'baz'};
h.put('foo', b);
assert.strictEqual(h.get('foo'), b);
});

it('should return undefined if there\'s no element', function () {
var h = new HashTable(8);
assert.equal(h.get('foo'), undefined);
assert.equal(h.get('bar'), undefined);
});

it('should handle hash conflicts', function () {
var h = new HashTable(4);
// Both keys are supposed to be pushed to the same position
assert.equal(h._position('a'), h._position('e'));
h.put('a', 'foo');
h.put('e', 'bar');
// the list in that position should keep both items
assert.equal(h._table[h._position('a')].length, 2);

// and still return them properly
assert.equal(h.get('a'), 'foo');
assert.equal(h.get('e'), 'bar');
});

it('should increase capacity if needed', function () {
var h = new HashTable(2);
assert.equal(h.capacity, 2);

h.put('foo', 'foo');
assert.equal(h.capacity, 2);

h.put('bar', 'bar');
assert.equal(h.capacity, 4);
});

it('should allow removing items', function () {
var h = new HashTable();
assert.equal(h.get('foo'), undefined);

h.put('foo', 'bar');
assert.equal(h.get('foo'), 'bar');

h.del('foo');
assert.equal(h.get('foo'), undefined);

// Deleting an unexistent item shouldn't do anything
h.del('foo');
assert.equal(h.get('foo'), undefined);
});
});

0 comments on commit 45e67e0

Please sign in to comment.