Skip to content

Commit

Permalink
Feat: Hash table (#50)
Browse files Browse the repository at this point in the history
* implementing hash table

* added hash table tests

* added hash table documentation
  • Loading branch information
zaguiini committed Nov 11, 2019
1 parent 474140b commit c9e18ad
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 89 deletions.
100 changes: 100 additions & 0 deletions docs/use-hash-table.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
name: useHashTable
route: /hash-table
---

import { Playground } from 'docz'
import { useHashTable } from '../src'
import { useState, useRef } from 'react'

# useHashTable

Just like `useState`, you can pass a default value handling the collisions with linked lists, but be sure to use the same hashing algorithm to compute the keys.
Also, the values are stored using linked lists with the following signature:<br />`{ data: 1, next: null }`

The second argument is an options object.

```js
function App() {
const hashFunction = useRef((key) =>
key.split('').reduce((curr, next) => curr + next.charCodeAt(0), 0)
)
// this hash function is really dumb,
// i'm only using it to show how we handle the collisions

const [key, setKey] = useState('')
const [value, setValue] = useState('')

const hashTable = useHashTable(undefined, {
// first argument is optional
hashFunction: hashFunction.current, // (key: any, bucketSize: number) => number
bucketSize: 30, // number
})

return (
<div>
<p>
Value for "{key}": {hashTable.get(key) || 'Not found'}
</p>
<div>
<div>
<p>
Key:{' '}
<input
onChange={({ target }) => setKey(target.value)}
value={key}
/>
</p>
<p>
Value:{' '}
<input
onChange={({ target }) => setValue(target.value)}
value={value}
/>
</p>
</div>
<button onClick={() => hashTable.set(key, value)}>Add value</button>
<button onClick={hashTable.clear}>Clear all values</button>
<button onClick={() => hashTable.delete(key)}>Delete value</button>
<button onClick={() => alert(hashTable.has(key))}>Has value?</button>
</div>
</div>
)
}
```
### Example
<Playground>
{() => {
const hashFunction = useRef((key) => key.split('').reduce((curr, next) => curr + next.charCodeAt(0), 0))
// this hashing algorithm is dumb as f,
// i'm only using it to show how we handle the collisions
const [key, setKey] = useState('')
const [value, setValue] = useState('')
const hashTable = useHashTable(undefined, {
hashFunction: hashFunction.current,
bucketSize: 30,
})
return (
<div>
<p>Value for "{key}": {hashTable.get(key) || 'Not found'}</p>
<div>
<div>
<p>Key: <input onChange={({ target }) => setKey(target.value)} value={key} /></p>
<p>Value: <input onChange={({ target }) => setValue(target.value)} value={value} /></p>
</div>
<button onClick={() => hashTable.set(key, value)}>Add value</button>{' '}
<button onClick={hashTable.clear}>Clear all values</button>{' '}
<button onClick={() => hashTable.delete(key)}>Delete value</button>{' '}
<button onClick={() => alert(hashTable.has(key))}>Has value?</button>{' '}
</div>
</div>
)
}}
</Playground>
3 changes: 2 additions & 1 deletion doczrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ module.exports = {
menu: [
'What is this?',
'useDeque',
'useHashTable',
'useLinkedList',
'useMap',
'useQueue',
'useSet',
'useStack',
'useWeakSet',
'useWeakMap',
'useWeakSet',
'Sorting algorithms',
],
themeConfig: {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './use-deque'
export * from './use-hash-table'
export * from './use-linked-list'
export * from './use-map'
export * from './use-queue'
Expand Down
111 changes: 111 additions & 0 deletions src/use-hash-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react'
import produce from 'immer'

import { NodeType, linkedListHandlers, LinkedListNode } from './use-linked-list'

const defaultHashFunction = (key: string, bucketSize: number) => {
let hash = 0

for (let i = 0; i < key.length; i++) {
hash = (hash << 5) - hash + key.charCodeAt(i)
hash = hash & hash
}

return Math.abs(hash) % bucketSize
}

type HashTableValues<TKey, TValue> = (NodeType<[TKey, TValue]> | null)[]

interface HashTableOptions {
hashFunction?: (key: any, bucketSize: number) => number
bucketSize?: number
}

export const useHashTable = <TKey extends any, TValue>(
initialHashTable?: HashTableValues<TKey, TValue>,
{ hashFunction = defaultHashFunction, bucketSize = 42 }: HashTableOptions = {}
) => {
const [table, setValue] = React.useState(() => {
if (initialHashTable) {
return initialHashTable
}

return new Array(bucketSize) as HashTableValues<TKey, TValue>
})

const handlers = React.useMemo(
() => ({
clear: () => setValue(new Array(bucketSize)),

delete: (key: TKey) => {
setValue((table) =>
produce(table, (draft: HashTableValues<TKey, TValue>) => {
const index = hashFunction(key, bucketSize)
const match = draft[index]

if (match) {
draft[index] = linkedListHandlers.remove<[TKey, TValue]>(
([currentKey]) => currentKey === key,
match
)
}
})
)
},

get: (key: TKey) => {
const index = hashFunction(key, bucketSize)
const match = table[index]

if (match) {
let found = linkedListHandlers.get<[TKey, TValue]>(
([currentKey]) => currentKey === key,
match
)

while (found) {
if (found.data[0] === key) {
return found.data[1]
}
}
}

return null
},

has: (key: TKey) => {
const index = hashFunction(key, bucketSize)

return !!table[index]
},

size: () => {
return table.reduce((curr, next) => {
return curr + linkedListHandlers.size(next)
}, 0)
},

set: (key: TKey, value: TValue) =>
setValue((table) =>
produce(table, (draft: HashTableValues<TKey, TValue>) => {
const index = hashFunction(key, bucketSize)

if (draft[index]) {
const size = linkedListHandlers.size(draft[index])

draft[index] = linkedListHandlers.addAt(
size,
[key, value],
draft[index]
)
} else {
draft[index] = LinkedListNode([key, value])
}
})
),
}),
[bucketSize, hashFunction, table]
)

return handlers
}

0 comments on commit c9e18ad

Please sign in to comment.