├── .coveralls.yml ├── .gitignore ├── .prettierrc ├── docs ├── components │ └── LinkedListIterator.tsx ├── index.mdx ├── use-set.mdx ├── use-weak-set.mdx ├── use-weak-map.mdx ├── use-map.mdx ├── use-linked-list.mdx ├── sorting-algorithms.mdx ├── use-stack.mdx ├── use-queue.mdx ├── use-deque.mdx └── use-hash-table.mdx ├── src ├── index.ts ├── use-set.ts ├── use-insertion-sort.ts ├── use-map.ts ├── use-weak-set.ts ├── use-weak-map.ts ├── use-stack.ts ├── use-bubble-sort.ts ├── use-queue.ts ├── use-selection-sort.ts ├── use-deque.ts ├── use-hash-table.ts └── use-linked-list.ts ├── doczrc.js ├── test ├── use-bubble-sort.test.ts ├── use-insertion-sort.test.ts ├── use-selection-sort.test.ts ├── use-set.test.ts ├── use-weak-set.test.ts ├── use-weak-map.test.ts ├── use-map.test.ts ├── use-stack.test.ts ├── use-queue.test.ts ├── use-hash-table.test.ts ├── use-deque.test.ts └── use-linked-list.test.ts ├── .eslintrc ├── tsconfig.json ├── .travis.yml ├── LICENSE ├── README.md └── package.json /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: CrbM2hqFfIUvBiVpNFTWTeiIi2kcZESQ7 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .rts2_cache_cjs 5 | .rts2_cache_esm 6 | .rts2_cache_umd 7 | .rts2_cache_system 8 | dist 9 | 10 | .docz 11 | coverage 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "semi": false, 5 | "arrowParens": "always", 6 | "jsxSingleQuote": false, 7 | "jsxBracketSameLine": false, 8 | "tabWidth": 2, 9 | "useTabs": false, 10 | "bracketSpacing": true 11 | } 12 | -------------------------------------------------------------------------------- /docs/components/LinkedListIterator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Node } from '../../src' 3 | 4 | function LinkedListIterator({ head }: { head: Node | null }) { 5 | return ( 6 | head && ( 7 |
8 |

Data: {head.data}

9 | 10 |
11 | ) 12 | ) 13 | } 14 | 15 | export default LinkedListIterator 16 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-deque' 2 | export * from './use-hash-table' 3 | export * from './use-linked-list' 4 | export * from './use-map' 5 | export * from './use-queue' 6 | export * from './use-set' 7 | export * from './use-stack' 8 | export * from './use-weak-map' 9 | export * from './use-weak-set' 10 | 11 | export * from './use-bubble-sort' 12 | export * from './use-insertion-sort' 13 | export * from './use-selection-sort' 14 | -------------------------------------------------------------------------------- /doczrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'useDataStructures', 3 | typescript: true, 4 | menu: [ 5 | 'What is this?', 6 | 'useDeque', 7 | 'useHashTable', 8 | 'useLinkedList', 9 | 'useMap', 10 | 'useQueue', 11 | 'useSet', 12 | 'useStack', 13 | 'useWeakMap', 14 | 'useWeakSet', 15 | 'Sorting algorithms', 16 | ], 17 | themeConfig: { 18 | mode: 'dark', 19 | colors: { 20 | primary: '#3BCCA5', 21 | }, 22 | linesToScrollEditor: 100, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /test/use-bubble-sort.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | import { useBubbleSort } from '../src' 3 | 4 | describe('useBubbleSort', () => { 5 | it('sorts the values correctly without modifying the original array', () => { 6 | const unsortedArray = [64, 25, 12, 22, 11] 7 | const { result } = renderHook(() => useBubbleSort(unsortedArray)) 8 | 9 | expect(unsortedArray).toEqual([64, 25, 12, 22, 11]) 10 | expect(result.current).toEqual([11, 12, 22, 25, 64]) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /test/use-insertion-sort.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | import { useInsertionSort } from '../src' 3 | 4 | describe('useInsertionSort', () => { 5 | it('sorts the values correctly without modifying the original array', () => { 6 | const unsortedArray = [64, 25, 12, 22, 11] 7 | const { result } = renderHook(() => useInsertionSort(unsortedArray)) 8 | 9 | expect(unsortedArray).toEqual([64, 25, 12, 22, 11]) 10 | expect(result.current).toEqual([11, 12, 22, 25, 64]) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /test/use-selection-sort.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | import { useSelectionSort } from '../src' 3 | 4 | describe('useSelectionSort', () => { 5 | it('sorts the values correctly without modifying the original array', () => { 6 | const unsortedArray = [64, 25, 12, 22, 11] 7 | const { result } = renderHook(() => useSelectionSort(unsortedArray)) 8 | 9 | expect(unsortedArray).toEqual([64, 25, 12, 22, 11]) 10 | expect(result.current).toEqual([11, 12, 22, 25, 64]) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "react-app", 4 | "prettier/@typescript-eslint", 5 | "plugin:prettier/recommended" 6 | ], 7 | "rules": { 8 | "prettier/prettier": "error", 9 | "@typescript-eslint/camelcase": "off", 10 | "@typescript-eslint/no-explicit-any": "off", 11 | "@typescript-eslint/no-unused-vars": "error", 12 | "@typescript-eslint/ban-ts-ignore": "off", 13 | "@typescript-eslint/explicit-function-return-type": "off", 14 | "@typescript-eslint/member-delimiter-style": "off", 15 | "react-hooks/rules-of-hooks": "error", 16 | "react-hooks/exhaustive-deps": "warn" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: What is this? 3 | route: / 4 | --- 5 | 6 | # useDataStructures 7 | 8 | The power of advanced data structures -- now on React! This library adds reactive capacities to data structures as React state. 9 | 10 | ## Motivation 11 | 12 | Ever wondered how to use more advances data structures with your favorite UI-rendering library?
Fear no more: `useDataStructures` is here to help! 13 | 14 | ## Contributing 15 | 16 | PRs are more than welcome! Feel free to fill as many as you want and collaborate with the community. This project embraces TypeScript and good practices, so be sure you are aware of them! 17 | 18 | ## License 19 | 20 | MIT 21 | -------------------------------------------------------------------------------- /src/use-set.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const useSet = (initialValue = new Set()) => { 4 | const [value, setValue] = React.useState(initialValue) 5 | 6 | const handlers = React.useMemo(() => { 7 | return Object.assign(value, { 8 | add: (item: T) => { 9 | setValue((oldSet) => { 10 | const newSet = new Set(oldSet) 11 | newSet.add(item) 12 | 13 | return newSet 14 | }) 15 | }, 16 | 17 | clear: () => setValue(new Set()), 18 | 19 | delete: (item: T) => { 20 | setValue((oldSet) => { 21 | const newSet = new Set(oldSet) 22 | newSet.delete(item) 23 | 24 | return newSet 25 | }) 26 | }, 27 | }) 28 | }, [value]) 29 | 30 | return handlers 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types", "test"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "baseUrl": "./", 24 | "paths": { 25 | "*": ["src/*", "node_modules/*"] 26 | }, 27 | "jsx": "react", 28 | "esModuleInterop": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/use-insertion-sort.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import produce from 'immer' 3 | 4 | type Predicate = (a: T, b: T) => boolean 5 | 6 | const defaultPredicate = (a: any, b: any) => a > b 7 | 8 | function insertionSort( 9 | array: T[], 10 | predicate: Predicate = defaultPredicate 11 | ) { 12 | for (let j = 1; j < array.length; j++) { 13 | const key = array[j] 14 | let i = j - 1 15 | 16 | while (i >= 0 && predicate(array[i], key)) { 17 | array[i + 1] = array[i] 18 | i -= 1 19 | } 20 | 21 | array[i + 1] = key 22 | } 23 | 24 | return array 25 | } 26 | 27 | export const useInsertionSort = (array: T[], predicate?: Predicate) => { 28 | const sortedArray = React.useMemo( 29 | () => produce(array, (draft) => insertionSort(draft as T[], predicate)), 30 | [array, predicate] 31 | ) 32 | 33 | return sortedArray 34 | } 35 | -------------------------------------------------------------------------------- /src/use-map.ts: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from 'react' 2 | 3 | export const useMap = ( 4 | initialState = new Map() 5 | ) => { 6 | const [value, setValue] = useState(initialState) 7 | 8 | const handlers = useMemo( 9 | () => 10 | Object.assign(value, { 11 | set: (key: TKey, value: TValue) => { 12 | setValue((oldMap) => { 13 | const newMap = new Map(oldMap) 14 | newMap.set(key, value) 15 | 16 | return newMap 17 | }) 18 | }, 19 | 20 | clear: () => setValue(new Map()), 21 | 22 | delete: (key: TKey) => { 23 | setValue((oldMap) => { 24 | const newMap = new Map(oldMap) 25 | newMap.delete(key) 26 | 27 | return newMap 28 | }) 29 | }, 30 | }), 31 | [value] 32 | ) 33 | 34 | return handlers 35 | } 36 | -------------------------------------------------------------------------------- /src/use-weak-set.ts: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from 'react' 2 | import produce from 'immer' 3 | 4 | export const useWeakSet = ( 5 | initialState: WeakSet = new WeakSet() 6 | ) => { 7 | const [value, setValue] = useState(initialState) 8 | 9 | const handlers = useMemo( 10 | () => ({ 11 | add: (item: T) => { 12 | setValue((oldWeakSet) => 13 | produce(oldWeakSet, (draft) => { 14 | draft.add(item) 15 | }) 16 | ) 17 | }, 18 | 19 | clear: () => setValue(new WeakSet()), 20 | 21 | delete: (item: T) => { 22 | setValue((oldWeakSet) => 23 | produce(oldWeakSet, (draft) => { 24 | draft.delete(item) 25 | }) 26 | ) 27 | }, 28 | 29 | has: (item: T) => value.has(item), 30 | }), 31 | [value] 32 | ) 33 | 34 | return handlers 35 | } 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - node 5 | 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | 11 | install: 12 | - yarn --ignore-scripts 13 | 14 | script: 15 | - yarn lint 16 | - yarn test:coverage:send 17 | - yarn build 18 | 19 | before_deploy: 20 | - git remote remove origin 21 | - git remote add origin https://${GH_TOKEN}@github.com/zaguiini/use-data-structures.git 22 | - git fetch && git checkout master 23 | - git config user.name "Luis Felipe Zaguini" 24 | - git config user.email "luisfelipezaguini@gmail.com" 25 | 26 | deploy: 27 | provider: script 28 | script: "npm config set '//registry.npmjs.org/:_authToken' '${API_TOKEN}' && yarn publish --new-version ${TRAVIS_TAG}" 29 | skip_cleanup: true 30 | on: 31 | tags: true 32 | branch: master 33 | 34 | after_deploy: 35 | - git commit --amend -m "v$TRAVIS_TAG [skip ci]" 36 | - git push origin master 37 | - yarn docz:build 38 | - yarn docz:publish 39 | -------------------------------------------------------------------------------- /src/use-weak-map.ts: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from 'react' 2 | import produce from 'immer' 3 | 4 | export const useWeakMap = ( 5 | initialState: WeakMap = new WeakMap() 6 | ) => { 7 | const [value, setValue] = useState(initialState) 8 | 9 | const handlers = useMemo( 10 | () => ({ 11 | clear: () => setValue(new WeakMap()), 12 | 13 | delete: (key: TKey) => { 14 | setValue((oldWeakMap) => 15 | produce(oldWeakMap, (draft) => { 16 | draft.delete(key) 17 | }) 18 | ) 19 | }, 20 | 21 | get: (key: TKey) => value.get(key), 22 | 23 | has: (key: TKey) => value.has(key), 24 | 25 | set: (key: TKey, value: TValue) => { 26 | setValue((oldWeakMap) => 27 | produce(oldWeakMap, (draft) => { 28 | draft.set(key, value) 29 | }) 30 | ) 31 | }, 32 | }), 33 | [value] 34 | ) 35 | 36 | return handlers 37 | } 38 | -------------------------------------------------------------------------------- /src/use-stack.ts: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from 'react' 2 | import produce from 'immer' 3 | 4 | export const useStack = (initialState: T[] = []) => { 5 | const [value, setValue] = useState(initialState) 6 | 7 | const handlers = useMemo( 8 | () => ({ 9 | isEmpty: () => { 10 | return value.length === 0 11 | }, 12 | 13 | peek: () => { 14 | let peeked = value[value.length - 1] 15 | return peeked 16 | }, 17 | 18 | pop: () => { 19 | if (value.length === 0) return 20 | 21 | let popped 22 | setValue((oldStack) => 23 | produce(oldStack, (draft: T[]) => { 24 | popped = draft.pop() 25 | }) 26 | ) 27 | return popped 28 | }, 29 | 30 | push: (item: T) => { 31 | setValue((oldStack) => 32 | produce(oldStack, (draft: T[]) => { 33 | draft.push(item) 34 | }) 35 | ) 36 | }, 37 | 38 | size: value.length, 39 | }), 40 | [value] 41 | ) 42 | 43 | return handlers 44 | } 45 | -------------------------------------------------------------------------------- /src/use-bubble-sort.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import produce from 'immer' 3 | 4 | type Predicate = (a: T, b: T) => boolean 5 | 6 | const defaultPredicate = (a: any, b: any) => a > b 7 | 8 | export const bubbleSort = ( 9 | array: T[], 10 | predicate: Predicate = defaultPredicate 11 | ) => { 12 | let swapped: boolean 13 | 14 | do { 15 | swapped = false 16 | 17 | for (let arrayIndex = 0; arrayIndex < array.length - 1; arrayIndex++) { 18 | if (predicate(array[arrayIndex], array[arrayIndex + 1])) { 19 | const tempElement = array[arrayIndex] 20 | array[arrayIndex] = array[arrayIndex + 1] 21 | array[arrayIndex + 1] = tempElement 22 | 23 | swapped = true 24 | } 25 | } 26 | } while (swapped) 27 | 28 | return array 29 | } 30 | 31 | export const useBubbleSort = (array: T[], predicate?: Predicate) => { 32 | const sortedArray = React.useMemo( 33 | () => produce(array, (draft) => bubbleSort(draft as T[], predicate)), 34 | [array, predicate] 35 | ) 36 | 37 | return sortedArray 38 | } 39 | -------------------------------------------------------------------------------- /src/use-queue.ts: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from 'react' 2 | import produce from 'immer' 3 | 4 | export const useQueue = (initialValue: T[] = []) => { 5 | const [value, setValue] = useState(initialValue) 6 | 7 | const handlers = useMemo( 8 | () => ({ 9 | dequeue: () => { 10 | if (value.length === 0) return 11 | 12 | let shifted 13 | setValue((oldQueue) => 14 | produce(oldQueue, (draft: T[]) => { 15 | shifted = draft.pop() 16 | }) 17 | ) 18 | 19 | return shifted 20 | }, 21 | 22 | enqueue: (item: T) => { 23 | setValue((oldQueue) => 24 | produce(oldQueue, (draft: T[]) => { 25 | draft.unshift(item) 26 | }) 27 | ) 28 | }, 29 | 30 | isEmpty: () => { 31 | return value.length === 0 32 | }, 33 | 34 | peek: () => { 35 | let length = value.length 36 | if (length === 0) return 37 | 38 | return value[length - 1] 39 | }, 40 | 41 | size: value.length, 42 | }), 43 | [value] 44 | ) 45 | 46 | return handlers 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Luis Felipe Zaguini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/use-set.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useSet 3 | route: /set 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import { useSet } from '../src' 8 | 9 | # useSet 10 | 11 | Just like `useState`, you can pass a default value (e.g. `new Set([1, 2, 3])`) 12 | 13 | ```js 14 | function App() { 15 | set = useSet() 16 | 17 | const toggleExistence = () => { 18 | if (set.has(3)) { 19 | set.delete(3) 20 | } else { 21 | set.add(3) 22 | } 23 | 24 | return ( 25 | <> 26 |

Set has value 3: {set.has(3).toString()}

27 | 28 | 29 | ) 30 | } 31 | } 32 | ``` 33 | 34 | ### Example 35 | 36 | 37 | {() => { 38 | const set = useSet(); 39 | 40 | const toggleExistence = () => { 41 | if (set.has(3)) { 42 | set.delete(3) 43 | } else { 44 | set.add(3) 45 | } 46 | } 47 | 48 | return ( 49 | <> 50 |

Set has value 3: {set.has(3).toString()}

51 | 52 | 53 | ) 54 | 55 | }} 56 | 57 |
58 | -------------------------------------------------------------------------------- /src/use-selection-sort.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import produce from 'immer' 3 | 4 | type Predicate = (a: T, b: T) => boolean 5 | 6 | const defaultPredicate = (a: any, b: any) => a < b 7 | 8 | export const selectionSort = ( 9 | array: T[], 10 | predicate: Predicate = defaultPredicate 11 | ) => { 12 | for (let arrayIndex = 0; arrayIndex < array.length - 1; arrayIndex++) { 13 | let minIndex = arrayIndex 14 | 15 | for ( 16 | let subArrayIndex = arrayIndex + 1; 17 | subArrayIndex < array.length; 18 | subArrayIndex++ 19 | ) { 20 | if (predicate(array[subArrayIndex], array[minIndex])) { 21 | minIndex = subArrayIndex 22 | } 23 | } 24 | 25 | const minIndexElement = array[minIndex] 26 | array[minIndex] = array[arrayIndex] 27 | array[arrayIndex] = minIndexElement 28 | } 29 | 30 | return array 31 | } 32 | 33 | export const useSelectionSort = (array: T[], predicate?: Predicate) => { 34 | const sortedArray = React.useMemo( 35 | () => produce(array, (draft) => selectionSort(draft as T[], predicate)), 36 | [array, predicate] 37 | ) 38 | 39 | return sortedArray 40 | } 41 | -------------------------------------------------------------------------------- /test/use-set.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useSet } from '../src' 3 | 4 | describe('useSet', () => { 5 | it('adds a value', () => { 6 | const { result } = renderHook(() => useSet()) 7 | 8 | act(() => { 9 | result.current.add(3) 10 | }) 11 | 12 | expect(result.current.has(3)).toBe(true) 13 | }) 14 | 15 | it('clears all values', () => { 16 | const defaultSet = new Set() 17 | defaultSet.add(3) 18 | defaultSet.add(5) 19 | 20 | const { result } = renderHook(() => useSet(defaultSet)) 21 | 22 | act(() => { 23 | result.current.clear() 24 | }) 25 | 26 | expect(result.current.has(3)).toBe(false) 27 | expect(result.current.has(5)).toBe(false) 28 | expect(result.current.size).toBe(0) 29 | }) 30 | 31 | it('deletes a value', () => { 32 | const defaultSet = new Set() 33 | defaultSet.add(3) 34 | 35 | const { result } = renderHook(() => useSet(defaultSet)) 36 | 37 | act(() => { 38 | result.current.delete(3) 39 | }) 40 | 41 | expect(result.current.has(3)).toBe(false) 42 | expect(result.current.size).toBe(0) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /docs/use-weak-set.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useWeakSet 3 | route: /weak-set 4 | --- 5 | 6 | import { Playground } from 'docz' 7 | import { useWeakSet } from '../src' 8 | 9 | # useWeakSet 10 | 11 | Just like `useState`, you can pass a default value (e.g. `useWeakSet(new WeakSet([{}]))`) 12 | 13 | ```js 14 | import { useWeakSet } from 'use-data-structures' 15 | 16 | function App() { 17 | const weakSet = useWeakSet() 18 | const value = {} 19 | 20 | const toggleWeakSet = () => { 21 | if (weakSet.has(value)) { 22 | weakSet.delete(value) 23 | } else { 24 | weakSet.add(value) 25 | } 26 | 27 | alert(weakSet.has(value).toString()) 28 | } 29 | 30 | return 31 | } 32 | ``` 33 | 34 | 35 | {() => { 36 | const weakSet = useWeakSet(); 37 | const value = {} 38 | 39 | const toggleWeakSet = () => { 40 | if (weakSet.has(value)) { 41 | weakSet.delete(value) 42 | } else { 43 | weakSet.add(value) 44 | } 45 | 46 | alert(weakSet.has(value).toString()) 47 | } 48 | 49 | return 50 | 51 | }} 52 | 53 | 54 | -------------------------------------------------------------------------------- /docs/use-weak-map.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useWeakMap 3 | route: /weak-map 4 | --- 5 | 6 | import { Playground } from 'docz' 7 | import { useWeakMap } from '../src' 8 | 9 | # useWeakMap 10 | 11 | Just like `useState`, you can passa default value (e.g. `new WeakMap([[key, value]])`) 12 | 13 | ```js 14 | import { useWeakMap } from 'use-data-structures' 15 | 16 | function App() { 17 | const weakMap = useWeakMap() 18 | const key = {} 19 | 20 | const toggleExistence = () => { 21 | if (weakMap.has(key)) { 22 | weakMap.delete(key) 23 | } else { 24 | weakMap.set(key, 'Added value') 25 | } 26 | alert(weakMap.get(key)) 27 | } 28 | 29 | return 30 | } 31 | ``` 32 | 33 | 34 | {() => { 35 | const weakMap = useWeakMap(); 36 | const key = {} 37 | 38 | const toggleExistence = () => { 39 | if (weakMap.has(key)) { 40 | weakMap.delete(key) 41 | } else { 42 | weakMap.set(key, 'Added value') 43 | } 44 | alert(weakMap.get(key)) 45 | } 46 | 47 | return ( 48 | 49 | ) 50 | 51 | }} 52 | 53 | 54 | -------------------------------------------------------------------------------- /test/use-weak-set.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useWeakSet } from '../src' 3 | 4 | describe('useWeakSet', () => { 5 | it('should add a value', () => { 6 | let foo = {} 7 | 8 | const { result } = renderHook(() => useWeakSet()) 9 | 10 | act(() => { 11 | result.current.add(foo) 12 | }) 13 | 14 | expect(result.current.has(foo)).toBeTruthy() 15 | }) 16 | 17 | it('should delete a value', () => { 18 | let foo = {} 19 | 20 | const { result } = renderHook(() => useWeakSet(new WeakSet([foo]))) 21 | 22 | act(() => { 23 | result.current.delete(foo) 24 | }) 25 | 26 | expect(result.current.has(foo)).toBeFalsy() 27 | }) 28 | 29 | it('should clear all values', () => { 30 | let foo = {}, 31 | bar = {} 32 | 33 | const { result } = renderHook(() => useWeakSet(new WeakSet([foo, bar]))) 34 | 35 | expect(result.current.has(foo)).toBeTruthy() 36 | expect(result.current.has(bar)).toBeTruthy() 37 | 38 | act(() => { 39 | result.current.clear() 40 | }) 41 | 42 | expect(result.current.has(foo)).toBeFalsy() 43 | expect(result.current.has(bar)).toBeFalsy() 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /docs/use-map.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useMap 3 | route: /map 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import { useMap } from '../src' 8 | 9 | # useMap 10 | 11 | Just like `useState`, you can pass a default value (e.g. `new Map([['key', 'value']])`) 12 | 13 | ```js 14 | function App() { 15 | const map = useMap() 16 | 17 | const toggleExistence = () => { 18 | if (map.has('key')) { 19 | map.delete('key') 20 | } else { 21 | map.set('key', 'value') 22 | } 23 | } 24 | 25 | return ( 26 | <> 27 |

Map has value for 'key': {map.has('key').toString()}

28 | 29 | 30 | ) 31 | } 32 | ``` 33 | 34 | ### Example 35 | 36 | 37 | {() => { 38 | const map = useMap(); 39 | 40 | const toggleExistence = () => { 41 | if (map.has('key')) { 42 | map.delete('key'); 43 | } else { 44 | map.set('key', 'value'); 45 | } 46 | }; 47 | 48 | return ( 49 | <> 50 |

Map has value for 'key': {map.has('key').toString()}

51 | 52 | 53 | ); 54 | 55 | }} 56 | 57 |
58 | -------------------------------------------------------------------------------- /docs/use-linked-list.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useLinkedList 3 | route: /linked-list 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import { useLinkedList } from '../src' 8 | import LinkedListIterator from './components/LinkedListIterator' 9 | 10 | # useLinkedList 11 | 12 | Just like `useState`, you can pass a default value (e.g. `{ data: 1, next: null }`) 13 | 14 | ```js 15 | function App() { 16 | const [head, handlers] = useLinkedList() 17 | 18 | return ( 19 | <> 20 | 23 |
24 | 25 | 26 | ) 27 | } 28 | ``` 29 | 30 | ### Example 31 | 32 | 33 | {() => { 34 | const [head, handlers] = useLinkedList() 35 | const input = React.useRef() 36 | 37 | return ( 38 | <> 39 | 42 | {' '} 43 | 44 | 45 |
46 | 47 | 48 | ) 49 | 50 | }} 51 | 52 |
53 | -------------------------------------------------------------------------------- /src/use-deque.ts: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from 'react' 2 | import produce from 'immer' 3 | 4 | export const useDeque = (initialState: T[] = []) => { 5 | const [value, setValue] = useState(initialState) 6 | 7 | const handlers = useMemo( 8 | () => ({ 9 | addFront: (item: T) => { 10 | setValue((oldDeque) => 11 | produce(oldDeque, (draft: T[]) => { 12 | draft.push(item) 13 | }) 14 | ) 15 | }, 16 | 17 | addRear: (item: T) => { 18 | setValue((oldDeque) => 19 | produce(oldDeque, (draft: T[]) => { 20 | draft.unshift(item) 21 | }) 22 | ) 23 | }, 24 | 25 | clear: () => setValue([] as T[]), 26 | 27 | isEmpty: () => value.length === 0, 28 | 29 | peekFront: () => value[value.length - 1], 30 | 31 | peekRear: () => { 32 | return value[0] 33 | }, 34 | 35 | removeFront: () => { 36 | setValue((oldDeque) => 37 | produce(oldDeque, (draft: T[]) => { 38 | draft.pop() 39 | }) 40 | ) 41 | }, 42 | 43 | removeRear: () => { 44 | setValue((oldDeque) => 45 | produce(oldDeque, (draft: T[]) => { 46 | draft.shift() 47 | }) 48 | ) 49 | }, 50 | 51 | size: value.length, 52 | }), 53 | [value] 54 | ) 55 | 56 | return handlers 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # useDataStructures · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zaguiini/use-data-structures/blob/master/LICENSE) [![npm version](https://img.shields.io/npm/v/use-data-structures.svg?style=flat)](https://www.npmjs.com/package/use-data-structures) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/zaguiini/use-data-structures/issues) [![Coverage Status](https://coveralls.io/repos/github/zaguiini/use-data-structures/badge.svg?branch=master)](https://coveralls.io/github/zaguiini/use-data-structures?branch=master) [![Build Status](https://api.travis-ci.com/zaguiini/use-data-structures.svg?branch=master)](https://api.travis-ci.com/zaguiini/use-data-structures.svg?branch=master) 2 | 3 | The power of advanced data structures -- now on React! 4 | This library adds reactive capacities to data structures as React state. 5 | 6 | ## 💥 Installation 7 | 8 | `yarn add use-data-structures` 9 | 10 | Powered by [TSDX](https://github.com/jaredpalmer/tsdx), TypeScript typing definitions are included by default. 11 | For the moment we don't have Flow typings but PRs are welcome! 12 | 13 | ## 📖 Documentation and examples 14 | 15 | You can check it out [here](https://use-data-structures.surge.sh/) 16 | 17 | ## 🖖 Contributing 18 | 19 | PRs are more than welcome! Feel free to fill as many as you want and collaborate with the community. 20 | This project embraces TypeScript and good practices, so be sure you are aware of them! 21 | 22 | ## 👨‍⚖️ License 23 | 24 | MIT 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-data-structures", 3 | "version": "0.11.0", 4 | "license": "MIT", 5 | "author": "Luis Felipe Zaguini", 6 | "main": "dist/index.js", 7 | "module": "dist/use-ds.esm.js", 8 | "typings": "dist/index.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "start": "tsdx watch", 14 | "build": "tsdx build", 15 | "test": "tsdx test", 16 | "test:coverage": "yarn test --coverage", 17 | "test:coverage:send": "yarn test:coverage && cat ./coverage/lcov.info | coveralls", 18 | "lint": "tsdx lint './src/**/*.{ts,tsx}'", 19 | "docz:dev": "docz dev", 20 | "docz:build": "docz build && cp .docz/dist/index.html .docz/dist/200.html", 21 | "docz:publish": "npx surge .docz/dist use-data-structures.surge.sh" 22 | }, 23 | "peerDependencies": { 24 | "react": "^16.8.0" 25 | }, 26 | "husky": { 27 | "hooks": { 28 | "pre-commit": "yarn lint && yarn test:coverage" 29 | } 30 | }, 31 | "devDependencies": { 32 | "@testing-library/react-hooks": "^3.1.1", 33 | "@types/jest": "^24.0.21", 34 | "@types/react": "^16.9.11", 35 | "coveralls": "^3.0.7", 36 | "docz": "^1.3.2", 37 | "docz-theme-default": "^1.2.0", 38 | "husky": "^3.0.9", 39 | "react": "^16.8.0", 40 | "react-test-renderer": "^16.11.0", 41 | "tsdx": "^0.11.0", 42 | "tslib": "^1.10.0", 43 | "typescript": "^3.6.4" 44 | }, 45 | "dependencies": { 46 | "immer": "^5.0.0" 47 | }, 48 | "jest": { 49 | "coverageThreshold": { 50 | "global": { 51 | "lines": 95 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/sorting-algorithms.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Sorting algorithms 3 | route: /sorting-algorithms 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import { useBubbleSort } from '../src' 8 | 9 | # Sorting algorithms 10 | 11 | All the sorting algorithms follow the same pattern. Here are the algorithms we implemented: 12 | 13 | - `useBubbleSort` 14 | - `useInsertionSort` 15 | - `useSelectionSort` 16 | 17 | We are **NOT** changing your array, we are creating a shallow copy of it. 18 | 19 | ### Usage 20 | 21 | You can pass a custom predicate as the second argument to the hook to change how the array will be sorted. 22 | 23 | Example: `(a, b) => a > b` to sort the list in descending order. 24 | 25 | ```js 26 | import { useBubbleSort } from 'use-data-structures' 27 | 28 | function App() { 29 | const unsortedArray = [64, 25, 12, 22, 11] 30 | const sortedArray = useBubbleSort(unsortedArray) 31 | 32 | return ( 33 |
34 |

Unsorted array

35 |

{unsortedArray.join(', ').toString()}

36 | 37 |

Sorted array

38 |

{sortedArray.join(', ').toString()}

39 |
40 | ) 41 | } 42 | ``` 43 | 44 | ### Example 45 | 46 | 47 | {() => { 48 | const unsortedArray = [64, 25, 12, 22, 11]; 49 | const sortedArray = useBubbleSort(unsortedArray); 50 | 51 | return ( 52 |
53 |

Unsorted array

54 |

{ unsortedArray.join(", ").toString()}

55 | 56 |

Sorted array

57 |

{ sortedArray.join(", ").toString()}

58 |
59 | ); 60 | 61 | }} 62 | 63 |
64 | -------------------------------------------------------------------------------- /test/use-weak-map.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useWeakMap } from '../src' 3 | 4 | describe('useWeakMap', () => { 5 | it('should set a correct value', () => { 6 | let key = {} 7 | const { result } = renderHook(() => useWeakMap()) 8 | 9 | act(() => { 10 | result.current.set(key, 'test object') 11 | }) 12 | 13 | expect(result.current.has(key)).toBeTruthy() 14 | }) 15 | 16 | it('should get a value', () => { 17 | let key = {} 18 | const { result } = renderHook(() => 19 | useWeakMap(new WeakMap([[key, 'test']])) 20 | ) 21 | 22 | const value = result.current.get(key) 23 | 24 | expect(value).toBe('test') 25 | }) 26 | 27 | it('should try to get a value', () => { 28 | let key = {} 29 | const { result } = renderHook(() => useWeakMap(new WeakMap())) 30 | 31 | const value = result.current.get(key) 32 | 33 | expect(value).toBeUndefined() 34 | }) 35 | 36 | it('should delete a value', () => { 37 | let key = {} 38 | const { result } = renderHook(() => 39 | useWeakMap(new WeakMap([[key, 'test']])) 40 | ) 41 | 42 | act(() => { 43 | result.current.delete(key) 44 | }) 45 | 46 | expect(result.current.has(key)).toBeFalsy() 47 | }) 48 | 49 | it('should clear the weakMap', () => { 50 | const key = {} 51 | const { result } = renderHook(() => useWeakMap(new WeakMap([[key, 1]]))) 52 | 53 | expect(result.current.has(key)).toBeTruthy() 54 | 55 | act(() => { 56 | result.current.clear() 57 | }) 58 | 59 | expect(result.current.has(key)).toBeFalsy() 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /test/use-map.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useMap } from '../src' 3 | 4 | describe('useMap', () => { 5 | it('should add a value', () => { 6 | const { result } = renderHook(() => useMap()) 7 | 8 | act(() => { 9 | result.current.set('key', 'value') 10 | }) 11 | 12 | expect(result.current.size).toBe(1) 13 | expect(result.current.has('key')).toBeTruthy() 14 | }) 15 | 16 | it('should add a value to the initial state', () => { 17 | const initialState = new Map([['key1', 'value1']]) 18 | const { result } = renderHook(() => useMap(initialState)) 19 | 20 | act(() => { 21 | result.current.set('key2', 'value2') 22 | }) 23 | 24 | expect(result.current.size).toBe(2) 25 | expect(result.current.has('key1')).toBeTruthy() 26 | expect(result.current.has('key2')).toBeTruthy() 27 | }) 28 | 29 | it('should clear all values', () => { 30 | const initialState = new Map([['key1', 'value1']]) 31 | const { result } = renderHook(() => useMap(initialState)) 32 | 33 | expect(result.current.size).toBe(1) 34 | expect(result.current.has('key1')).toBeTruthy() 35 | 36 | act(() => { 37 | result.current.clear() 38 | }) 39 | 40 | expect(result.current.size).toBe(0) 41 | expect(result.current.has('key1')).toBeFalsy() 42 | }) 43 | 44 | it('should delete a value', () => { 45 | const initialState = new Map([['key1', 'value1']]) 46 | const { result } = renderHook(() => useMap(initialState)) 47 | 48 | expect(result.current.size).toBe(1) 49 | expect(result.current.has('key1')).toBeTruthy() 50 | 51 | act(() => { 52 | result.current.delete('key1') 53 | }) 54 | 55 | expect(result.current.size).toBe(0) 56 | expect(result.current.has('key1')).toBeFalsy() 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/use-stack.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useStack } from '../src' 3 | 4 | describe('useStack', () => { 5 | it('should return true for an empty stack', () => { 6 | const { result } = renderHook(() => useStack()) 7 | 8 | expect(result.current.isEmpty()).toBeTruthy() 9 | }) 10 | 11 | it('should return false because the stack contains one element', () => { 12 | const { result } = renderHook(() => useStack([1])) 13 | 14 | expect(result.current.isEmpty()).toBeFalsy() 15 | }) 16 | 17 | it('should push a value', () => { 18 | const { result } = renderHook(() => useStack()) 19 | 20 | act(() => { 21 | result.current.push(1) 22 | }) 23 | 24 | expect(result.current.isEmpty()).toBeFalsy() 25 | expect(result.current.size).toBe(1) 26 | }) 27 | 28 | it('should pop a value', () => { 29 | const { result } = renderHook(() => useStack([1])) 30 | 31 | let popped 32 | act(() => { 33 | popped = result.current.pop() 34 | }) 35 | 36 | expect(popped).toBe(1) 37 | expect(result.current.isEmpty()).toBeTruthy() 38 | }) 39 | 40 | it('should peek a value', () => { 41 | const { result } = renderHook(() => useStack([1, 2, 3])) 42 | 43 | let peeked 44 | act(() => { 45 | peeked = result.current.peek() 46 | }) 47 | 48 | expect(peeked).toBe(3) 49 | }) 50 | 51 | it('should try to pop a value from an empty array', () => { 52 | const { result } = renderHook(() => useStack()) 53 | 54 | act(() => { 55 | result.current.pop() 56 | }) 57 | 58 | expect(result.current.isEmpty()).toBeTruthy() 59 | }) 60 | 61 | it('should return correct size', () => { 62 | const { result } = renderHook(() => useStack([1, 2, 3])) 63 | 64 | expect(result.current.size).toBe(3) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /test/use-queue.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useQueue } from '../src' 3 | 4 | describe('useQueue', () => { 5 | it('should return the correct size', () => { 6 | const { result } = renderHook(() => useQueue([1, 2, 3])) 7 | 8 | expect(result.current.size).toBe(3) 9 | }) 10 | 11 | it('should check empty queue', () => { 12 | const { result } = renderHook(() => useQueue()) 13 | 14 | expect(result.current.isEmpty()).toBeTruthy() 15 | }) 16 | 17 | it('should enqueue a value', () => { 18 | const { result } = renderHook(() => useQueue()) 19 | 20 | act(() => { 21 | result.current.enqueue(1) 22 | }) 23 | 24 | expect(result.current.size).toBe(1) 25 | }) 26 | 27 | it('should dequeue a value', () => { 28 | const { result } = renderHook(() => useQueue([6, 5])) 29 | 30 | expect(result.current.size).toBe(2) 31 | 32 | let shifted 33 | act(() => { 34 | shifted = result.current.dequeue() 35 | }) 36 | 37 | expect(shifted).toBe(5) 38 | expect(result.current.size).toBe(1) 39 | }) 40 | 41 | it('should try to dequeue a value', () => { 42 | const { result } = renderHook(() => useQueue()) 43 | 44 | let shifted 45 | act(() => { 46 | shifted = result.current.dequeue() 47 | }) 48 | 49 | expect(shifted).toBeUndefined() 50 | }) 51 | 52 | it('should check the front value', () => { 53 | const { result } = renderHook(() => useQueue([5, 6])) 54 | 55 | let front = result.current.peek() 56 | 57 | expect(front).toBe(6) 58 | expect(result.current.size).toBe(2) 59 | }) 60 | 61 | it('should try to check the front value', () => { 62 | const { result } = renderHook(() => useQueue()) 63 | 64 | let front = result.current.peek() 65 | expect(front).toBeUndefined() 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /docs/use-stack.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useStack 3 | route: /stack 4 | --- 5 | 6 | import { Playground } from 'docz' 7 | import { useStack } from '../src' 8 | import { useRef } from 'react' 9 | 10 | # useStack 11 | 12 | Just like `useState`, you can pass a default value (e.g. `useStack([1, 2, 3])`) 13 | 14 | ```js 15 | import { useStack } from 'use-data-structures' 16 | 17 | function App() { 18 | const input = useRef() 19 | const stack = useStack() 20 | 21 | const addItem = () => { 22 | stack.push(input.current.value) 23 | input.current.value = null 24 | } 25 | 26 | const removeItem = () => { 27 | stack.pop() 28 | } 29 | 30 | const peekStack = () => { 31 | alert(stack.peek()) 32 | } 33 | 34 | return ( 35 |
36 |

Stack size: {stack.size}

37 |
38 | {' '} 39 | {' '} 40 | {' '} 41 | 42 |
43 |
44 | ) 45 | } 46 | ``` 47 | 48 | 49 | {() => { 50 | const input = useRef() 51 | const stack = useStack() 52 | 53 | const addItem = () => { 54 | stack.push(input.current.value) 55 | input.current.value = null 56 | } 57 | 58 | const removeItem = () => { 59 | stack.pop() 60 | } 61 | 62 | const peekStack = () => { 63 | alert(stack.peek()) 64 | } 65 | 66 | return ( 67 |
68 |

Stack size: {stack.size}

69 |
70 | {' '} 71 | {' '} 72 | {' '} 73 | 74 |
75 |
76 | ) 77 | 78 | }} 79 | 80 |
81 | -------------------------------------------------------------------------------- /docs/use-queue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useQueue 3 | route: /queue 4 | --- 5 | 6 | import { Playground } from 'docz' 7 | import { useQueue } from '../src' 8 | import { useRef } from 'react' 9 | 10 | # useQueue 11 | 12 | Just like `useState`, you can pass a default value (e.g. `useQueue([1, 2, 3])`) 13 | 14 | ```js 15 | import { useQueue } from 'use-data-structures' 16 | 17 | function App() { 18 | const input = useRef() 19 | const queue = useQueue() 20 | 21 | const addQueue = () => { 22 | queue.enqueue(input.current.value) 23 | input.current.value = null 24 | } 25 | 26 | const removeQueue = () => { 27 | queue.dequeue() 28 | } 29 | 30 | const peekQueue = () => { 31 | alert(queue.peek()) 32 | } 33 | 34 | return ( 35 |
36 |

Queue size: {queue.size}

37 |
38 | {' '} 39 | {' '} 40 | {' '} 41 | 42 |
43 |
44 | ) 45 | } 46 | ``` 47 | 48 | 49 | {() => { 50 | const input = useRef(); 51 | const queue = useQueue(); 52 | 53 | const addQueue = () => { 54 | queue.enqueue(input.current.value); 55 | input.current.value = null; 56 | } 57 | 58 | const removeQueue = () => { 59 | queue.dequeue(); 60 | } 61 | 62 | const peekQueue = () => { 63 | alert(queue.peek()) 64 | } 65 | 66 | return ( 67 |
68 |

Queue size: {queue.size}

69 |
70 | {" "} 71 | {" "} 72 | {" "} 73 | 74 |
75 |
76 | ) 77 | 78 | }} 79 | 80 |
81 | -------------------------------------------------------------------------------- /docs/use-deque.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useDeque 3 | route: /deque 4 | --- 5 | 6 | import { Playground } from 'docz' 7 | import { useDeque } from '../src' 8 | import { useRef } from 'react' 9 | 10 | ```js 11 | import { useDeque } from 'use-data-structures' 12 | 13 | function App() { 14 | const deque = useDeque() 15 | const input = useRef() 16 | 17 | return ( 18 |
19 |

Deque size: {deque.size}

20 |
21 | {' '} 22 | {' '} 25 | {' '} 28 | {' '} 29 | {' '} 30 | {' '} 31 | {' '} 32 | {' '} 33 |
34 |
35 | ) 36 | } 37 | ``` 38 | 39 | 40 | {() => { 41 | const deque = useDeque() 42 | const input = useRef() 43 | 44 | return ( 45 |
46 |

Deque size: {deque.size}

47 |
48 | {" "} 49 | {" "} 50 | {" "} 51 | {" "} 52 | {" "} 53 | {" "} 54 | {" "} 55 | {" "} 56 |
57 |
58 | ) 59 | 60 | }} 61 | 62 |
63 | -------------------------------------------------------------------------------- /test/use-hash-table.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useHashTable } from '../src' 3 | 4 | describe('useHashTable', () => { 5 | it('should set a value', () => { 6 | const { result } = renderHook(() => useHashTable()) 7 | 8 | act(() => { 9 | result.current.set('123', '456') 10 | }) 11 | 12 | expect(result.current.has('123')).toBe(true) 13 | }) 14 | 15 | it('should handle collisions', () => { 16 | const hashFunction = (key: string) => 17 | key.split('').reduce((curr, next) => curr + next.charCodeAt(0), 0) 18 | 19 | const { result } = renderHook(() => 20 | useHashTable(undefined, { 21 | hashFunction, 22 | bucketSize: 5, 23 | }) 24 | ) 25 | 26 | act(() => { 27 | result.current.set('123', '456') 28 | result.current.set('321', '456') 29 | }) 30 | 31 | expect(result.current.get('123')).toBe('456') 32 | expect(result.current.get('321')).toBe('456') 33 | }) 34 | 35 | it('should get a value', () => { 36 | const { result } = renderHook(() => useHashTable()) 37 | 38 | act(() => { 39 | result.current.set('123', '456') 40 | }) 41 | 42 | expect(result.current.get('123')).toBe('456') 43 | }) 44 | 45 | it('should delete a value', () => { 46 | const { result } = renderHook(() => useHashTable()) 47 | 48 | act(() => { 49 | result.current.set('123', '456') 50 | }) 51 | 52 | act(() => { 53 | result.current.delete('123') 54 | }) 55 | 56 | act(() => { 57 | result.current.delete('4t6') 58 | }) 59 | 60 | expect(result.current.get('123')).toBe(null) 61 | }) 62 | 63 | it('should clear all values', () => { 64 | const { result } = renderHook(() => useHashTable()) 65 | 66 | act(() => { 67 | result.current.set('123', '456') 68 | }) 69 | 70 | act(() => { 71 | result.current.clear() 72 | }) 73 | 74 | expect(result.current.get('123')).toBe(null) 75 | }) 76 | 77 | it('should return the correct size', () => { 78 | const { result } = renderHook(() => useHashTable()) 79 | 80 | act(() => { 81 | result.current.set('123', '456') 82 | }) 83 | 84 | act(() => { 85 | result.current.set('456', '123') 86 | }) 87 | 88 | expect(result.current.size()).toBe(2) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /test/use-deque.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useDeque } from '../src' 3 | 4 | describe('useDeque', () => { 5 | it('should check the size', () => { 6 | const { result } = renderHook(() => useDeque([4, 3, 2, 1])) 7 | 8 | expect(result.current.size).toBe(4) 9 | }) 10 | 11 | it('should check an empty deque', () => { 12 | const { result } = renderHook(() => useDeque()) 13 | 14 | expect(result.current.isEmpty()).toBeTruthy() 15 | }) 16 | 17 | it('should check an full deque', () => { 18 | const { result } = renderHook(() => useDeque([2, 1])) 19 | 20 | expect(result.current.isEmpty()).toBeFalsy() 21 | }) 22 | 23 | it('should peek a front value', () => { 24 | const initialValue = [3, 2, 777] 25 | 26 | const { result } = renderHook(() => useDeque(initialValue)) 27 | 28 | let front 29 | act(() => { 30 | front = result.current.peekFront() 31 | }) 32 | 33 | expect(front).toBe(777) 34 | }) 35 | 36 | it('should peek a rear value', () => { 37 | const initialValue = [55, 2, 10, 7] 38 | 39 | const { result } = renderHook(() => useDeque(initialValue)) 40 | 41 | let rear 42 | act(() => { 43 | rear = result.current.peekRear() 44 | }) 45 | 46 | expect(rear).toBe(55) 47 | }) 48 | 49 | it('should add a front value', () => { 50 | const initialValue = [3, 2, 1] 51 | 52 | const { result } = renderHook(() => useDeque(initialValue)) 53 | 54 | act(() => { 55 | result.current.addFront(55) 56 | }) 57 | 58 | expect(result.current.size).toBe(4) 59 | expect(result.current.peekFront()).toBe(55) 60 | }) 61 | 62 | it('should add a rear value', () => { 63 | const initialValue = [3, 2, 1] 64 | 65 | const { result } = renderHook(() => useDeque(initialValue)) 66 | 67 | act(() => { 68 | result.current.addRear(55) 69 | }) 70 | 71 | expect(result.current.size).toBe(4) 72 | expect(result.current.peekRear()).toBe(55) 73 | }) 74 | 75 | it('should remove a front value', () => { 76 | const initialValue = [3, 2, 55, 7] 77 | 78 | const { result } = renderHook(() => useDeque(initialValue)) 79 | 80 | act(() => { 81 | result.current.removeFront() 82 | }) 83 | 84 | expect(result.current.size).toBe(3) 85 | expect(result.current.peekFront()).toBe(55) 86 | }) 87 | 88 | it('should remove a rear value', () => { 89 | const initialValue = [55, 99, 7] 90 | 91 | const { result } = renderHook(() => useDeque(initialValue)) 92 | 93 | act(() => { 94 | result.current.removeRear() 95 | }) 96 | 97 | expect(result.current.size).toBe(2) 98 | expect(result.current.peekRear()).toBe(99) 99 | }) 100 | 101 | it('should clear deque', () => { 102 | const { result } = renderHook(() => useDeque([1, 2, 3])) 103 | 104 | act(() => { 105 | result.current.clear() 106 | }) 107 | 108 | expect(result.current.isEmpty()).toBeTruthy() 109 | }) 110 | }) 111 | -------------------------------------------------------------------------------- /src/use-hash-table.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import produce from 'immer' 3 | 4 | import { NodeType, linkedListHandlers, LinkedListNode } from './use-linked-list' 5 | 6 | const defaultHashFunction = (key: string, bucketSize: number) => { 7 | let hash = 0 8 | 9 | for (let i = 0; i < key.length; i++) { 10 | hash = (hash << 5) - hash + key.charCodeAt(i) 11 | hash = hash & hash 12 | } 13 | 14 | return Math.abs(hash) % bucketSize 15 | } 16 | 17 | type HashTableValues = (NodeType<[TKey, TValue]> | null)[] 18 | 19 | interface HashTableOptions { 20 | hashFunction?: (key: any, bucketSize: number) => number 21 | bucketSize?: number 22 | } 23 | 24 | export const useHashTable = ( 25 | initialHashTable?: HashTableValues, 26 | { hashFunction = defaultHashFunction, bucketSize = 42 }: HashTableOptions = {} 27 | ) => { 28 | const [table, setValue] = React.useState(() => { 29 | if (initialHashTable) { 30 | return initialHashTable 31 | } 32 | 33 | return new Array(bucketSize) as HashTableValues 34 | }) 35 | 36 | const handlers = React.useMemo( 37 | () => ({ 38 | clear: () => setValue(new Array(bucketSize)), 39 | 40 | delete: (key: TKey) => { 41 | setValue((table) => 42 | produce(table, (draft: HashTableValues) => { 43 | const index = hashFunction(key, bucketSize) 44 | const match = draft[index] 45 | 46 | if (match) { 47 | draft[index] = linkedListHandlers.remove<[TKey, TValue]>( 48 | ([currentKey]) => currentKey === key, 49 | match 50 | ) 51 | } 52 | }) 53 | ) 54 | }, 55 | 56 | get: (key: TKey) => { 57 | const index = hashFunction(key, bucketSize) 58 | const match = table[index] 59 | 60 | if (match) { 61 | let found = linkedListHandlers.get<[TKey, TValue]>( 62 | ([currentKey]) => currentKey === key, 63 | match 64 | ) 65 | 66 | while (found) { 67 | if (found.data[0] === key) { 68 | return found.data[1] 69 | } 70 | } 71 | } 72 | 73 | return null 74 | }, 75 | 76 | has: (key: TKey) => { 77 | const index = hashFunction(key, bucketSize) 78 | 79 | return !!table[index] 80 | }, 81 | 82 | size: () => { 83 | return table.reduce((curr, next) => { 84 | return curr + linkedListHandlers.size(next) 85 | }, 0) 86 | }, 87 | 88 | set: (key: TKey, value: TValue) => 89 | setValue((table) => 90 | produce(table, (draft: HashTableValues) => { 91 | const index = hashFunction(key, bucketSize) 92 | 93 | if (draft[index]) { 94 | const size = linkedListHandlers.size(draft[index]) 95 | 96 | draft[index] = linkedListHandlers.addAt( 97 | size, 98 | [key, value], 99 | draft[index] 100 | ) 101 | } else { 102 | draft[index] = LinkedListNode([key, value]) 103 | } 104 | }) 105 | ), 106 | }), 107 | [bucketSize, hashFunction, table] 108 | ) 109 | 110 | return handlers 111 | } 112 | -------------------------------------------------------------------------------- /docs/use-hash-table.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: useHashTable 3 | route: /hash-table 4 | --- 5 | 6 | import { Playground } from 'docz' 7 | import { useHashTable } from '../src' 8 | import { useState, useRef } from 'react' 9 | 10 | # useHashTable 11 | 12 | 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. 13 | Also, the values are stored using linked lists with the following signature:
`{ data: 1, next: null }` 14 | 15 | The second argument is an options object. 16 | 17 | ```js 18 | function App() { 19 | const hashFunction = useRef((key) => 20 | key.split('').reduce((curr, next) => curr + next.charCodeAt(0), 0) 21 | ) 22 | // this hash function is really dumb, 23 | // i'm only using it to show how we handle the collisions 24 | 25 | const [key, setKey] = useState('') 26 | const [value, setValue] = useState('') 27 | 28 | const hashTable = useHashTable(undefined, { 29 | // first argument is optional 30 | hashFunction: hashFunction.current, // (key: any, bucketSize: number) => number 31 | bucketSize: 30, // number 32 | }) 33 | 34 | return ( 35 |
36 |

37 | Value for "{key}": {hashTable.get(key) || 'Not found'} 38 |

39 |
40 |
41 |

42 | Key:{' '} 43 | setKey(target.value)} 45 | value={key} 46 | /> 47 |

48 |

49 | Value:{' '} 50 | setValue(target.value)} 52 | value={value} 53 | /> 54 |

55 |
56 | 57 | 58 | 59 | 60 |
61 |
62 | ) 63 | } 64 | ``` 65 | 66 | ### Example 67 | 68 | 69 | {() => { 70 | const hashFunction = useRef((key) => key.split('').reduce((curr, next) => curr + next.charCodeAt(0), 0)) 71 | // this hashing algorithm is dumb as f, 72 | // i'm only using it to show how we handle the collisions 73 | 74 | const [key, setKey] = useState('') 75 | const [value, setValue] = useState('') 76 | 77 | const hashTable = useHashTable(undefined, { 78 | hashFunction: hashFunction.current, 79 | bucketSize: 30, 80 | }) 81 | 82 | return ( 83 |
84 |

Value for "{key}": {hashTable.get(key) || 'Not found'}

85 |
86 |
87 |

Key: setKey(target.value)} value={key} />

88 |

Value: setValue(target.value)} value={value} />

89 |
90 | {' '} 91 | {' '} 92 | {' '} 93 | {' '} 94 |
95 |
96 | ) 97 | 98 | }} 99 | 100 |
101 | -------------------------------------------------------------------------------- /src/use-linked-list.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export interface NodeType { 4 | data: T 5 | next: NodeType | null 6 | } 7 | 8 | export const LinkedListNode = ( 9 | data: T, 10 | next: NodeType | null = null 11 | ) => ({ 12 | data, 13 | next, 14 | }) 15 | 16 | interface Handlers { 17 | add: (data: T) => void 18 | addAt: (data: T, index: number) => void 19 | clear: () => void 20 | get: (value: T) => NodeType | null 21 | getAt: (index: number) => void 22 | prepend: (data: T) => void 23 | remove: (value: T) => void 24 | removeAt: (index: number) => void 25 | size: () => number 26 | } 27 | 28 | export const linkedListHandlers = { 29 | addAt: (index: number, data: T, head: NodeType | null) => { 30 | if (index === 0) { 31 | return LinkedListNode(data, head) 32 | } 33 | 34 | let previous = linkedListHandlers.getAt(index - 1, head) 35 | 36 | if (previous) { 37 | previous.next = LinkedListNode(data, previous.next) 38 | } 39 | 40 | return head 41 | }, 42 | 43 | get: ( 44 | match: (currentNodeData: T) => boolean, 45 | head: NodeType | null 46 | ) => { 47 | let current = head 48 | 49 | while (current) { 50 | if (match(current.data)) { 51 | return current 52 | } 53 | 54 | current = current.next 55 | } 56 | 57 | return null 58 | }, 59 | 60 | getAt: (index: number, head: NodeType | null) => { 61 | let counter = 0 62 | let current = head 63 | 64 | while (current) { 65 | if (counter === index) { 66 | return current 67 | } 68 | 69 | counter++ 70 | current = current.next 71 | } 72 | 73 | return null 74 | }, 75 | 76 | prepend: (data: T, head: NodeType | null) => 77 | linkedListHandlers.addAt(0, data, head), 78 | 79 | remove: ( 80 | match: (currentNodeData: T) => boolean, 81 | list: NodeType | null 82 | ) => { 83 | if (!list) { 84 | return list 85 | } 86 | 87 | if (match(list.data)) { 88 | return list.next ? list.next : null 89 | } 90 | 91 | const head = LinkedListNode(list.data, list.next) 92 | let previous = head 93 | let current = head 94 | 95 | while (current.next && !match(current.data)) { 96 | previous = current 97 | current = current.next 98 | } 99 | 100 | if (previous && match(current.data)) { 101 | previous.next = current.next 102 | } 103 | 104 | return head 105 | }, 106 | 107 | removeAt: (index: number, list: NodeType | null) => { 108 | if (!list) { 109 | return list 110 | } else if (index === 0) { 111 | return list.next 112 | } 113 | 114 | let head = LinkedListNode(list.data, list.next) 115 | 116 | const previous = linkedListHandlers.getAt(index - 1, head) 117 | 118 | if (previous) { 119 | previous.next = previous.next ? previous.next.next : null 120 | } 121 | 122 | return head 123 | }, 124 | 125 | size: (head: NodeType | null) => { 126 | let size = 0 127 | let current = head 128 | 129 | while (current) { 130 | current = current.next 131 | size++ 132 | } 133 | 134 | return size 135 | }, 136 | } 137 | 138 | export const useLinkedList = ( 139 | initialHead?: NodeType 140 | ): [NodeType | null, Handlers] => { 141 | const [head, setList] = React.useState(initialHead || null) 142 | 143 | const handlers = React.useMemo(() => { 144 | const addAt = (data: T, index: number) => { 145 | setList((list) => { 146 | return linkedListHandlers.addAt( 147 | index, 148 | data, 149 | list ? Object.assign({}, list) : null 150 | ) 151 | }) 152 | } 153 | 154 | const prepend = (data: T) => { 155 | setList((list) => { 156 | return linkedListHandlers.prepend( 157 | data, 158 | list ? Object.assign({}, list) : null 159 | ) 160 | }) 161 | } 162 | 163 | const removeAt = (index: number) => { 164 | setList((list) => linkedListHandlers.removeAt(index, list)) 165 | } 166 | 167 | const clear = () => setList(null) 168 | 169 | const size = () => linkedListHandlers.size(head) 170 | 171 | const add = (data: T) => addAt(data, size()) 172 | 173 | const get = (value: T) => 174 | linkedListHandlers.get((data) => data === value, head) 175 | 176 | const getAt = (index: number) => linkedListHandlers.getAt(index, head) 177 | 178 | const remove = (value: T) => 179 | setList((list) => 180 | linkedListHandlers.remove((data) => data === value, list) 181 | ) 182 | 183 | return { 184 | add, 185 | addAt, 186 | clear, 187 | get, 188 | getAt, 189 | prepend, 190 | remove, 191 | removeAt, 192 | size, 193 | } 194 | }, [head]) 195 | 196 | return [head, handlers] 197 | } 198 | -------------------------------------------------------------------------------- /test/use-linked-list.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks' 2 | import { useLinkedList } from '../src' 3 | 4 | describe('useLinkedList', () => { 5 | it('should add a value', () => { 6 | const { result } = renderHook(() => useLinkedList()) 7 | act(() => result.current[1].add(1)) 8 | 9 | expect(result.current[1].size()).toBe(1) 10 | expect(result.current[0]).toEqual({ data: 1, next: null }) 11 | }) 12 | 13 | it('should add a value at an specific index', () => { 14 | const { result } = renderHook(() => useLinkedList()) 15 | 16 | act(() => result.current[1].add(1)) 17 | act(() => result.current[1].add(3)) 18 | act(() => result.current[1].add(4)) 19 | act(() => result.current[1].addAt(2, 1)) 20 | 21 | expect(result.current[1].size()).toBe(4) 22 | expect(result.current[0]).toEqual({ 23 | data: 1, 24 | next: { 25 | data: 2, 26 | next: { 27 | data: 3, 28 | next: { 29 | data: 4, 30 | next: null, 31 | }, 32 | }, 33 | }, 34 | }) 35 | }) 36 | 37 | it('should clear all values', () => { 38 | const { result } = renderHook(() => useLinkedList()) 39 | 40 | act(() => result.current[1].add(1)) 41 | act(() => result.current[1].clear()) 42 | 43 | expect(result.current[1].size()).toBe(0) 44 | 45 | expect(result.current[0]).toEqual(null) 46 | }) 47 | 48 | it('should get a value', () => { 49 | const { result } = renderHook(() => useLinkedList()) 50 | 51 | act(() => result.current[1].add(1)) 52 | act(() => result.current[1].add(2)) 53 | 54 | expect(result.current[1].get(2)).toEqual({ data: 2, next: null }) 55 | }) 56 | 57 | it('should not get a value', () => { 58 | const { result } = renderHook(() => useLinkedList()) 59 | 60 | expect(result.current[1].get(2)).toEqual(null) 61 | }) 62 | 63 | it('should get a value at an specific index', () => { 64 | const { result } = renderHook(() => useLinkedList()) 65 | 66 | act(() => result.current[1].add(1)) 67 | act(() => result.current[1].add(2)) 68 | 69 | expect(result.current[1].getAt(1)).toEqual({ data: 2, next: null }) 70 | }) 71 | 72 | it('should not get a value at an specific index', () => { 73 | const { result } = renderHook(() => useLinkedList()) 74 | 75 | act(() => result.current[1].add(1)) 76 | act(() => result.current[1].add(2)) 77 | 78 | expect(result.current[1].getAt(5)).toEqual(null) 79 | }) 80 | 81 | it('should prepend a value', () => { 82 | const { result } = renderHook(() => useLinkedList()) 83 | 84 | act(() => result.current[1].add(1)) 85 | act(() => result.current[1].prepend(0)) 86 | 87 | expect(result.current[1].size()).toBe(2) 88 | 89 | expect(result.current[0]).toEqual({ 90 | data: 0, 91 | next: { 92 | data: 1, 93 | next: null, 94 | }, 95 | }) 96 | }) 97 | 98 | it('should remove a value', () => { 99 | const { result } = renderHook(() => useLinkedList()) 100 | act(() => result.current[1].add(1)) 101 | act(() => result.current[1].add(2)) 102 | act(() => result.current[1].add(3)) 103 | act(() => result.current[1].remove(2)) 104 | 105 | expect(result.current[1].size()).toBe(2) 106 | expect(result.current[0]).toEqual({ 107 | data: 1, 108 | next: { 109 | data: 3, 110 | next: null, 111 | }, 112 | }) 113 | }) 114 | 115 | it('should remove the first value', () => { 116 | const { result } = renderHook(() => useLinkedList()) 117 | act(() => result.current[1].add(1)) 118 | act(() => result.current[1].remove(1)) 119 | 120 | expect(result.current[1].size()).toBe(0) 121 | }) 122 | 123 | it('should not remove since there are no values', () => { 124 | const { result } = renderHook(() => useLinkedList()) 125 | act(() => result.current[1].remove(1)) 126 | 127 | expect(result.current[1].size()).toBe(0) 128 | }) 129 | 130 | it('should remove the first value', () => { 131 | const { result } = renderHook(() => useLinkedList()) 132 | act(() => result.current[1].add(1)) 133 | act(() => result.current[1].add(2)) 134 | act(() => result.current[1].add(3)) 135 | act(() => result.current[1].removeAt(0)) 136 | 137 | expect(result.current[1].size()).toBe(2) 138 | expect(result.current[0]).toEqual({ 139 | data: 2, 140 | next: { 141 | data: 3, 142 | next: null, 143 | }, 144 | }) 145 | }) 146 | 147 | it('should remove a value at an specific index', () => { 148 | const { result } = renderHook(() => useLinkedList()) 149 | act(() => result.current[1].add(1)) 150 | act(() => result.current[1].add(2)) 151 | act(() => result.current[1].add(3)) 152 | act(() => result.current[1].removeAt(1)) 153 | 154 | expect(result.current[1].size()).toBe(2) 155 | expect(result.current[0]).toEqual({ 156 | data: 1, 157 | next: { 158 | data: 3, 159 | next: null, 160 | }, 161 | }) 162 | }) 163 | 164 | it('should not remove at an specific index since there are no values', () => { 165 | const { result } = renderHook(() => useLinkedList()) 166 | act(() => result.current[1].removeAt(1)) 167 | 168 | expect(result.current[1].size()).toBe(0) 169 | }) 170 | 171 | it('should get the correct size', () => { 172 | const { result } = renderHook(() => useLinkedList()) 173 | act(() => result.current[1].add(1)) 174 | act(() => result.current[1].add(2)) 175 | act(() => result.current[1].add(3)) 176 | 177 | expect(result.current[1].size()).toBe(3) 178 | }) 179 | }) 180 | --------------------------------------------------------------------------------