-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* implementing hash table * added hash table tests * added hash table documentation
- Loading branch information
Showing
6 changed files
with
429 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.