Skip to content

Commit

Permalink
Merge a329e54 into 64d0d58
Browse files Browse the repository at this point in the history
  • Loading branch information
divyanshyadav committed Nov 26, 2020
2 parents 64d0d58 + a329e54 commit c182ca7
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 2 deletions.
15 changes: 14 additions & 1 deletion README.md
Expand Up @@ -14,12 +14,13 @@

Light weight javascript data structures library

- HashMap (New!)
- Binary Search Tree
- Stack
- Queue
- Heap
- Graph
- Disjoint-set (New!)
- Disjoint-set

## Installation and Usage

Expand Down Expand Up @@ -123,6 +124,18 @@ ds.isConnected('a', 'd') // true

```

### HashMap

```js
const { HashMap } = require("data-structures-again");

const map = new HashMap()

map.set('name', 'john')
map.get('name') // john

```

## License

MIT.
4 changes: 3 additions & 1 deletion index.js
Expand Up @@ -4,12 +4,14 @@ const Queue = require('./src/queue')
const Heap = require('./src/heap')
const Graph = require('./src/graph')
const DisjointSet = require('./src/disjoint-set')
const HashMap = require('./src/hash-map')

module.exports = {
BST,
Stack,
Queue,
Heap,
Graph,
DisjointSet
DisjointSet,
HashMap
}
9 changes: 9 additions & 0 deletions index.test.js
Expand Up @@ -71,3 +71,12 @@ test('disjoint-set example', () => {
expect(ds.find('d')).toBe('a')
expect(ds.isConnected('a', 'd')).toBeTruthy()
})

test('hash-map example', () => {
const { HashMap } = require('data-structures-again')

const map = new HashMap()

map.set('name', 'john')
expect(map.get('name')).toBe('john')
})
77 changes: 77 additions & 0 deletions src/hash-map/README.md
@@ -0,0 +1,77 @@
# Hash table
- key value pair implementation of dictionary ADT

## operations
- insert
- remove
- find

## Hash table techniques
- Direct addressing
- Memory wastage
- only support integer keys
- Open addressing

## Hash function
- Maps keys to indices of array
- Comprise of
- Hash code map(use to convert key to integer value)
- Compression map(use to compress the key value so that it lies in the hash table boundaries)

## A good hash function
- Quick to compute
- A good hash function distributes keys uniformly across the hash table
- Minimizes the probability of collision
- It should map same key to same index

## Hash-Code Maps (hashing techniques)
- Integer cast
- For numeric types less than 32bits
- If key is less than 32bits
- Component sum
- For numeric types with more than 32 bits(eg long, double)
- Bad for strings
- As two strings can have the same characters
- Polynomial accumulation
- For strings of a natural language, combine the character values(ASCII) a0,a1,a2...a(n-1) by viewing them as coefficient of a polynomial: a0 + a1*x + ... + x^n-1 * a(n-1)
- Choices of x = 33, 37, 39 or 41 gives at most 6 collisions on a vocabulary of 50,000 english words

## Compression Maps
- Use the remainder
- h(k) = k mod m, k is the key and m is the size of table
- h(k) lies in between 0 and m - 1
- Bad
- if m is power of 2 let say e, then h(k) gives the e least significant bits of k
- all keys with the same ending goes to the same place
- Good
- if m is prime and not too close to exact power of 2
- helps ensure uniform distribution
- Use
- h(k) = Floor(m(kA mod 1))
- k is the key, m is the table size
- 0 < A < 1
- m can be size of 2^p
- Optimal choice of A depends on the characteristics of the data
- knuth says use
- Fibonacci hashing
- Conjugate of the golder ratio
- (under-root(5) - 1) / 2
- Multiply, Add and Divide (MAD)
- h(k) = | ak + b | mod N
- k is the key,
- a should not be the multiple of N
- Same formula use in (pseudo) random number generators

## Universal hashing
- Choose random hash function from the set of multiple hash function at the init of the hash table

## Collision resolution techniques
- Chaining (using linked list)
- Open Addressing
- All elements are stored in the table i.e n <= m
- Each table entry can contains either an element or null
- Linear probing
- Double hashing

## Load factor
- Given hash table T with m slots holding n elements, the load factor is defined as a = n/m
103 changes: 103 additions & 0 deletions src/hash-map/chaining.js
@@ -0,0 +1,103 @@
const { hashCodePoly } = require('../utils/hash')

class HashMap {
constructor (hashFn = hashCodePoly) {
this.array = new Array(1)
this.length = 0
this.hash = hashFn
}

_resize () {
const newArray = new Array(this.array.length * 2)

this.array.forEach(item => {
if (item === undefined) {
return
}

item.forEach(([key, value]) => {
const idx = this.hash(key) % newArray.length

if (newArray[idx] === undefined) {
newArray[idx] = []
}

newArray[idx].push([key, value])
})
})

this.array = newArray
}

set (key, value) {
const loadFactor = this.length / this.array.length

if (loadFactor > 0.8) {
this._resize()
}

const idx = this.hash(key) % this.array.length

if (this.has(key)) {
this.array[idx] = this.array[idx].map((i) => {
if (i[0] === key) {
return [i[0], value]
}

return i
})
} else {
if (this.array[idx] === undefined) {
this.array[idx] = []
}

this.array[idx].push([key, value])
this.length++
}
}

get (key) {
if (!this.has(key)) {
return -1
}

const idx = this.hash(key) % this.array.length
for (let i = 0; i < this.array[idx].length; i++) {
const [k, value] = this.array[idx][i]

if (k === key) {
return value
}
}
}

delete (key) {
if (!this.has(key)) {
return
}

const idx = this.hash(key) % this.array.length
this.array[idx] = this.array[idx].filter(([k]) => k !== key)
this.length--
}

has (key) {
const idx = this.hash(key) % this.array.length

if (this.array[idx] === undefined) {
return false
}

if (this.array[idx].find(([k]) => k === key) === undefined) {
return false
}

return true
}

size () {
return this.length
}
}

module.exports = HashMap
47 changes: 47 additions & 0 deletions src/hash-map/chaining.test.js
@@ -0,0 +1,47 @@
const HashMap = require('./chaining')

test('set and get', () => {
const map = new HashMap()
map.set('name', 'red')
expect(map.get('name')).toBe('red')
})

test('has', () => {
const map = new HashMap()
map.set('hi', 'john')
expect(map.has('hi')).toBe(true)
expect(map.has('red')).toBe(false)
})

test('delete', () => {
const map = new HashMap()
map.set('hi', 'john')
map.delete('hi')
expect(map.has('hi')).toBe(false)
})

test('size', () => {
const map = new HashMap()
map.set('hi', 'john')
expect(map.size()).toBe(1)
})

test('resize', () => {
const map = new HashMap()
map.set('name', 'john')
map.set('age', 5)
map.set('dob', '1/2/3')
expect(map.array.length).toBe(4)
})

test('case 1', () => {
const map = new HashMap()
map.set(1, 1)
map.set(2, 2)
expect(map.get(1)).toBe(1)
expect(map.get(3)).toBe(-1)
map.set(2, 1)
expect(map.get(2)).toBe(1)
map.delete(2)
expect(map.get(2)).toBe(-1)
})
3 changes: 3 additions & 0 deletions src/hash-map/index.js
@@ -0,0 +1,3 @@
const HashMapChaining = require('./chaining')

module.exports = HashMapChaining
74 changes: 74 additions & 0 deletions src/hash-map/linear-probing.js
@@ -0,0 +1,74 @@
const { hashCodePoly } = require('../utils/hash')

class HashMap {
constructor (size, hashFn = hashCodePoly) {
this.array = new Array(size).fill(null)
this.size = size
this.total = 0
this.X = [-Infinity, -Infinity]
this.hashCode = hashFn
}

searchEmptyIndex (key) {
const intKey = this.hashCode(key)
let index = (intKey % this.size)

while (this.array[index] !== null && this.array[index] !== this.X) {
index = (index + 1) % this.size
}

return index
}

get (key) {
const intKey = this.hashCode(key)
let index = (intKey % this.size)
const init = index

while (this.array[index] !== null) {
if (this.array[index][0] === intKey) {
return this.array[index][1]
}

index = (index + 1) % this.size

if (init === index) {
return
}
}
}

set (key, value) {
if (this.total === this.size) {
throw new Error('Hash table is full')
}

const intKey = this.hashCode(key)
let index = this.searchEmptyIndex(key)

this.array[index] = [intKey, value]
this.total += 1
}

remove (key) {
const intKey = this.hashCode(key)
let index = (intKey % this.size)
const init = index

while (this.array[index]) {
if (this.array[index][0] === intKey) {
this.array[index] = this.X
this.total -= 1
return
}

index = (index + 1) % this.size

if (index === init) {
return
}
}
}
}

module.exports = HashMap
23 changes: 23 additions & 0 deletions src/hash-map/linear-probing.test.js
@@ -0,0 +1,23 @@
const HashTable = require('./linear-probing')

test('set and get', () => {
const map = new HashTable(13)
map.set('john', 43)

expect(map.get('john')).toBe(43)
})

test('remove', () => {
const map = new HashTable(13)
map.set('john', 43)
map.remove('john')

expect(map.get('john')).toBe(undefined)
})

test('table full check', () => {
const map = new HashTable(1)
map.set('john', 43)

expect(() => map.set('red john', 43)).toThrowError('full')
})

0 comments on commit c182ca7

Please sign in to comment.