├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── .nojekyll ├── assets │ ├── hierarchy.js │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── classes │ ├── data-structures_linkedList.default.html │ └── data-structures_node.default.html ├── functions │ ├── search_sequentialSearch.default.html │ ├── sort_bubbleSort.default.html │ └── sort_insertionSort.default.html ├── index.html ├── modules.html ├── modules │ ├── data-structures_linkedList.html │ ├── data-structures_node.html │ ├── search_sequentialSearch.html │ ├── sort_bubbleSort.html │ └── sort_insertionSort.html └── types │ └── search_sequentialSearch.Predicate.html ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── data-structures │ ├── linkedList.test.ts │ ├── linkedList.ts │ ├── node.test.ts │ └── node.ts ├── search │ ├── sequentialSearch.test.ts │ └── sequentialSearch.ts └── sort │ ├── bubbleSort.test.ts │ ├── bubbleSort.ts │ ├── insertionSort.test.ts │ └── insertionSort.ts ├── tsconfig.json ├── tslint.json └── typedoc.json /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [18.x, 20.x, 22.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm run lint 32 | - run: npm test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | node_modules 4 | coverage 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stoimen Popov 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Example](https://github.com/stoimen/algorithms/actions/workflows/node.js.yml/badge.svg) 2 | 3 | ## Algorithms 4 | 5 | GitHub port of my [blog posts on algorithms](http://www.stoimen.com/category/algorithms/) intended to be covered on JS. Feel free to add algorithms written in different programming languages! 6 | 7 | For more info visit the [wiki page](https://github.com/stoimen/algorithms/wiki) 8 | 9 | ## API Reference 10 | 11 | Find the API [here](https://stoimen.github.io/algorithms/) 12 | 13 | ## Book! 14 | 15 | The book is getting there… [the infographics are on github](https://github.com/stoimen/infographics) 16 | 17 | *** 18 | 19 | ### Search 20 | 21 | * [Sequential Search](https://github.com/stoimen/algorithms/wiki/Sequential-Search) 22 | * [Linear Search in Sorted Lists](https://github.com/stoimen/algorithms/wiki/Linear-Search-in-Sorted-Lists) 23 | * [Jump Search](https://github.com/stoimen/algorithms/wiki/Jump-Search) 24 | * [Binary Search](https://github.com/stoimen/algorithms/wiki/Binary-Search) 25 | * [Interpolation Search](https://github.com/stoimen/algorithms/wiki/Interpolation-Search) 26 | 27 | ### Order statistics 28 | 29 | * [Minimum and Maximum](https://github.com/stoimen/algorithms/wiki/Minimum-and-Maximum) 30 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg==" -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-code-background: #FFFFFF; 3 | --dark-code-background: #1E1E1E; 4 | } 5 | 6 | @media (prefers-color-scheme: light) { :root { 7 | --code-background: var(--light-code-background); 8 | } } 9 | 10 | @media (prefers-color-scheme: dark) { :root { 11 | --code-background: var(--dark-code-background); 12 | } } 13 | 14 | :root[data-theme='light'] { 15 | --code-background: var(--light-code-background); 16 | } 17 | 18 | :root[data-theme='dark'] { 19 | --code-background: var(--dark-code-background); 20 | } 21 | 22 | pre, code { background: var(--code-background); } 23 | -------------------------------------------------------------------------------- /docs/assets/icons.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | addIcons(); 3 | function addIcons() { 4 | if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); 5 | const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); 6 | svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; 7 | svg.style.display = "none"; 8 | if (location.protocol === "file:") updateUseElements(); 9 | } 10 | 11 | function updateUseElements() { 12 | document.querySelectorAll("use").forEach(el => { 13 | if (el.getAttribute("href").includes("#icon-")) { 14 | el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); 15 | } 16 | }); 17 | } 18 | })() -------------------------------------------------------------------------------- /docs/assets/icons.svg: -------------------------------------------------------------------------------- 1 | MMNEPVFCICPMFPCPTTAAATR -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJytkl9rwjAUR7/Lfe7mLJtu/Qw+DHyUUtLkSoMxcbk3sDH63cU/1DTaToav5dzD6a9d/QLjN0MBSrB4IvZBcvBIkIFstFEeLRSrjjLablAtNDFksBPcQAFbp4JBmiSG6sI+N7w1kMFGWwVFflutcC2CibzSCKJx7/mm75/m723ZZp3YOoV/1x6ox3YejSOFcSOh8LKZEH4FtKyFWR4fXGefwCoF7yr/9Ki0FBytwT+7EWl3kOhfPubTtzzKv9pkHaxk7eyw++Yws9f+Ks7zwI9Yh7o2uDwB6UbOc3UB/vdVozdIdPeUa0voD+cjhT3mIZF943Bn2ZZ7bcxnKw==" -------------------------------------------------------------------------------- /docs/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "eJy1mV2PozYYhf+Le+smvC8QSO62d5WqdqWReoOiEROcDl0CKTjZXUX575X5iO3YZD2EvWJEfI6P7cfG9lxIXX1tyCa5kC95mZENUlKmB0Y2JEt5+mvD69OOn2rWLIu8/MKyP/KGE0pOdUE25FBlp4I1y7uir7Lo4p0fCkLJrkibhjVkQ8iVDnUBxrI2tk9PhbTuBY+se4mlCkqOac1K/rgVMkgIstm7quyKV/XUML/oHh8NNnipPeVhcEv4ztJscrRePHcmnubF5Ey9eIZM6AUSqbxsWM0/7TmbPpKdR9p7/JyEv7F9VbMnI74NJnNnPKTHydE67dyJjqfmfXKkXjx3ppodqvP0MbzJ587VfH1i+Hrx3Jn2dXX4VNfp98nBhEPaO8ydjlfPZePVz0v2wuu8/OeJaM1gMEe20Z1CWWXsh3sEUWj23UFrOmlf0GZ+AOxfb/+y3YQALavVIHbPYunwqbsUM9OU/clIKG0XIDQT4vSy+XKU7NuUoepl8+U41uw8IUcveyqH/hVg/M9pXdIwPkev3Kf5PK1jGsZn6RslSlrv3pcN++/ESp6nxUv7wli8unKv9+Uerl/orSMIZV2fa5blu5TLpZF/Pz7wvpV/vJCNNUEGWQWj6+j+VO54XpXjKZwW0x9nULq8qvny7fT2VrCXqjbPkuL3V/n7wy52a9mdoVuL7lKOtqTbfedVOd4YrchM7dE93Zukx71uKcnLjH0jmws5s7rJq5JsCC78xZpQss9ZkYnLgS4UJbvqcBBmlGTV7tT+ue2L/c3E10QU7kovPUITj8Jq4a18ChRx4Xu43dJkcGlLtC8GM/mmdQBCE6QQLaJgpQnBEIImREITn2K4iP1YE6IhRE3oE5oE1IeFp+t8Q+druoDQJLToAkMXaLqQ0GRl0YWGLtR0K0KTyKJbGbqVposITWKLLjJ0kaaLCU3WFl1s6GJNtyY0Ac8iXBvCtT70ggQAixJMauAOGwEDoE1rAUcnBwQP4Nu0JjugwwOCCbDhAyY/oAMEgTZbQtt0AZMm0HGCcHTGmESBjhQIUsAGI5hUgY4VRGMTDkyyQEcL4rblor1xqGtNukDHC1q+bBMBTMBAJwxbwmyTAU3CUCcMW8JsEwJNwvBubRLQoG1OoGV10glDAQ3aZgWahKFOGApU0DYr0OQKda5wlCs0uUKdKxSooG1GockV6lxhNFqvyRXqXKFABW2zEU2uUOcK16P1mlz1r9ov6ZnVnGW/d1/UJNFPaRfy2n9r/eErfyFINpcrJRCJ51V+Ydu3t4+s+K2ttD0oSSNPGnm9UdA/YzfDYZchPVF6Qu8Vdk8cnlH/XDvVoVzWyFpArQacfYaDtGK0UoxWTkbdFbi0CKSD72SgXQpLHyVJ+AGf4eZWGkXSyK1F7T2r1MdS78ZVd8RTOlVJAG6jfJSnG4UlZZQxcPU5a1GUxqDnZtFe8UqLtXRwmxXDbaySQplpbv1hHIma/mSp9A4oTXMj73YcV6IpzUO3mXQ7RitZlAYiurnII1LTnn0UN3WFc0NYP50YhsocRbdBNK4xi/Y/l0rH3S2eH3HsbjgVr1CZMG6gd5frioWSB9wCdf83kxZKCLcMt7tqJYYydOAGgrxWVmyUAQMHtreUHPMjK/KSkU2yvV7/B2F5Mkk="; -------------------------------------------------------------------------------- /docs/classes/data-structures_linkedList.default.html: -------------------------------------------------------------------------------- 1 | default | Algorithms

Represents a doubly linked list.

2 |

Type Parameters

  • T

Constructors

Properties

head 4 | tail 5 |

Methods

fromArray 6 | insertAfter 7 | insertBefore 8 | map 9 | push 10 | remove 11 | swap 12 | toArray 13 | toString 14 |

Constructors

Properties

head: null | default<T>
tail: null | default<T>

Methods

  • Fills the List with data from an array.

    16 |

    Parameters

    • array: T[]

      The array to fill the list with.

      17 |

    Returns void

  • Calls iteratee on each Node of the List. Alters the Node.

    26 |

    Parameters

    • iteratee: (node: default<T>) => void

      The function to call on each node.

      27 |

    Returns void

  • Returns all list nodes' data concatenated into a single string.

    39 |

    Returns string

    The concatenated string of all nodes' data.

    40 |
41 | -------------------------------------------------------------------------------- /docs/classes/data-structures_node.default.html: -------------------------------------------------------------------------------- 1 | default | Algorithms

Represents a node in a doubly linked list.

2 |

Type Parameters

  • T

Constructors

Properties

data 4 | next 5 | prev 6 |

Methods

Constructors

Properties

data: T
next: null | default<T>
prev: null | default<T>

Methods

  • Creates a Node from an object.

    16 |

    Type Parameters

    • T

    Parameters

    • obj: { data: T }

      The object to create a node from.

      17 |
      • data: T

        The data to store in the node.

        18 |

    Returns default<T>

    The created node.

    19 |
20 | -------------------------------------------------------------------------------- /docs/functions/search_sequentialSearch.default.html: -------------------------------------------------------------------------------- 1 | default | Algorithms
  • Performs a sequential search on a linked list.

    2 |

    This function traverses the linked list one node at a time, 3 | applying the provided predicate to each node. It returns the first node 4 | for which the predicate returns true. If no node matches, it returns null.

    5 |

    Type Parameters

    • T

      The type of data stored in the nodes.

      6 |

    Parameters

    • list: default<T>

      The linked list to search through.

      7 |
    • predicate: Predicate<T>

      A function that determines whether a node satisfies the search condition.

      8 |

    Returns null | default<T>

    The first node matching the predicate, or null if no match is found.

    9 |
10 | -------------------------------------------------------------------------------- /docs/functions/sort_bubbleSort.default.html: -------------------------------------------------------------------------------- 1 | default | Algorithms
2 | -------------------------------------------------------------------------------- /docs/functions/sort_insertionSort.default.html: -------------------------------------------------------------------------------- 1 | default | Algorithms
  • Sorts a linked list using the insertion sort algorithm. 2 | The algorithm iterates over the list, and for each node (starting from the second), 3 | finds the correct sorted position in the already-sorted portion (to its left) and 4 | re-inserts the node there.

    5 |

    Type Parameters

    • T

    Parameters

    • list: default<T>

      The linked list to sort.

      6 |
    • predicate: (a: default<T>, b: default<T>) => boolean

      A comparison function that returns true if the first node should come after the second. 7 | For example, (a, b) => a.data > b.data will sort numbers in ascending order.

      8 |

    Returns void

    If the list or predicate is not provided or invalid.

    9 |
10 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Algorithms

Algorithms

Example

2 |

GitHub port of my blog posts on algorithms intended to be covered on JS. Feel free to add algorithms written in different programming languages!

3 |

For more info visit the wiki page

4 |

Find the API here

5 |

The book is getting there… the infographics are on github

6 |
7 | 14 | 17 |
18 | -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | Algorithms

Algorithms

Modules

data-structures/linkedList
data-structures/node
search/sequentialSearch
sort/bubbleSort
sort/insertionSort
2 | -------------------------------------------------------------------------------- /docs/modules/data-structures_linkedList.html: -------------------------------------------------------------------------------- 1 | data-structures/linkedList | Algorithms

Module data-structures/linkedList

Classes

default
2 | -------------------------------------------------------------------------------- /docs/modules/data-structures_node.html: -------------------------------------------------------------------------------- 1 | data-structures/node | Algorithms

Module data-structures/node

Classes

default
2 | -------------------------------------------------------------------------------- /docs/modules/search_sequentialSearch.html: -------------------------------------------------------------------------------- 1 | search/sequentialSearch | Algorithms

Module search/sequentialSearch

Type Aliases

Predicate

Functions

default
2 | -------------------------------------------------------------------------------- /docs/modules/sort_bubbleSort.html: -------------------------------------------------------------------------------- 1 | sort/bubbleSort | Algorithms

Module sort/bubbleSort

Functions

default
2 | -------------------------------------------------------------------------------- /docs/modules/sort_insertionSort.html: -------------------------------------------------------------------------------- 1 | sort/insertionSort | Algorithms

Module sort/insertionSort

Functions

default
2 | -------------------------------------------------------------------------------- /docs/types/search_sequentialSearch.Predicate.html: -------------------------------------------------------------------------------- 1 | Predicate | Algorithms
Predicate: (node: default<T>) => boolean

A predicate function that checks whether a given node meets a condition.

2 |

Type Parameters

  • T

    The type of data stored in the node.

    3 |

Type declaration

    • (node: default<T>): boolean
    • Parameters

      • node: default<T>

        The node to evaluate.

        4 |

      Returns boolean

      True if the node meets the condition otherwise, false.

      5 |
6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Algorithms", 3 | "version": "1.0.0", 4 | "description": "Algorithms and data structures", 5 | "main": "index.js", 6 | "scripts": { 7 | "coverage": "npm test -- --coverage", 8 | "docs": "typedoc --options typedoc.json", 9 | "test": "jest", 10 | "lint": "tslint -p tsconfig.json" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/stoimen/algorithms.git" 15 | }, 16 | "keywords": [ 17 | "algorithms", 18 | "data", 19 | "structures", 20 | "computer", 21 | "science" 22 | ], 23 | "author": "popov@stoimen.com", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/stoimen/algorithms/issues" 27 | }, 28 | "homepage": "https://github.com/stoimen/algorithms#readme", 29 | "devDependencies": { 30 | "@types/jest": "^29.5.14", 31 | "@types/node": "^22.13.5", 32 | "jest": "^29.7.0", 33 | "ts-jest": "^29.2.6", 34 | "tslint": "^5.20.1", 35 | "tslint-config-prettier": "^1.18.0", 36 | "typedoc": "^0.27.9", 37 | "typescript": "^5.7.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/data-structures/linkedList.test.ts: -------------------------------------------------------------------------------- 1 | // List.test.ts 2 | import List from './linkedList' 3 | import Node from './node' 4 | 5 | describe('List', () => { 6 | let list: List 7 | 8 | beforeEach(() => { 9 | list = new List() 10 | }) 11 | 12 | it('should initialize an empty list', () => { 13 | expect(list.head).toBeNull() 14 | expect(list.tail).toBeNull() 15 | }) 16 | 17 | describe('push', () => { 18 | it('should push a node into an empty list', () => { 19 | const node = new Node(1) 20 | list.push(node) 21 | expect(list.head).toBe(node) 22 | expect(list.tail).toBe(node) 23 | expect(node.prev).toBeNull() 24 | expect(node.next).toBeNull() 25 | }) 26 | 27 | it('should push multiple nodes and update tail correctly', () => { 28 | const node1 = new Node(1) 29 | const node2 = new Node(2) 30 | const node3 = new Node(3) 31 | list.push(node1) 32 | list.push(node2) 33 | list.push(node3) 34 | 35 | expect(list.head).toBe(node1) 36 | expect(list.tail).toBe(node3) 37 | expect(node1.next).toBe(node2) 38 | expect(node2.prev).toBe(node1) 39 | expect(node2.next).toBe(node3) 40 | expect(node3.prev).toBe(node2) 41 | }) 42 | }) 43 | 44 | describe('insertAfter', () => { 45 | it('should insert a node after a target when the target has a next node', 46 | () => { 47 | // Build list: node1 -> node2 48 | const node1 = new Node(1) 49 | const node2 = new Node(2) 50 | list.push(node1) 51 | list.push(node2) 52 | 53 | // Insert node3 after node1: node1 -> node3 -> node2 54 | const node3 = new Node(3) 55 | list.insertAfter(node1, node3) 56 | 57 | expect(node1.next).toBe(node3) 58 | expect(node3.prev).toBe(node1) 59 | expect(node3.next).toBe(node2) 60 | expect(node2.prev).toBe(node3) 61 | expect(list.tail).toBe(node2) 62 | }) 63 | 64 | it('should insert a node after a target when the target is the tail', 65 | () => { 66 | const node1 = new Node(1) 67 | list.push(node1) 68 | 69 | // node1 is tail, so insertAfter should push node2 to the end. 70 | const node2 = new Node(2) 71 | list.insertAfter(node1, node2) 72 | 73 | expect(node1.next).toBe(node2) 74 | expect(node2.prev).toBe(node1) 75 | expect(list.tail).toBe(node2) 76 | }) 77 | }) 78 | 79 | describe('insertBefore', () => { 80 | it('should insert a node before a target when the target is the head', 81 | () => { 82 | const node1 = new Node(1) 83 | list.push(node1) 84 | 85 | const node0 = new Node(0) 86 | list.insertBefore(node1, node0) 87 | 88 | expect(list.head).toBe(node0) 89 | expect(node0.next).toBe(node1) 90 | expect(node1.prev).toBe(node0) 91 | }) 92 | 93 | it('should insert a node before a target when the target is not the head', 94 | () => { 95 | // Build list: node1 -> node2 96 | const node1 = new Node(1) 97 | const node2 = new Node(2) 98 | list.push(node1) 99 | list.push(node2) 100 | 101 | // Insert node between node1 and node2: node1 -> node1_5 -> node2 102 | const node15 = new Node(1.5) 103 | list.insertBefore(node2, node15) 104 | 105 | expect(node1.next).toBe(node15) 106 | expect(node15.prev).toBe(node1) 107 | expect(node15.next).toBe(node2) 108 | expect(node2.prev).toBe(node15) 109 | }) 110 | }) 111 | 112 | describe('remove', () => { 113 | it('should remove a middle node', () => { 114 | // Build list: node1 -> node2 -> node3 115 | const node1 = new Node(1) 116 | const node2 = new Node(2) 117 | const node3 = new Node(3) 118 | list.push(node1) 119 | list.push(node2) 120 | list.push(node3) 121 | 122 | // Remove node2 123 | list.remove(node2) 124 | 125 | expect(node1.next).toBe(node3) 126 | expect(node3.prev).toBe(node1) 127 | expect(node2.prev).toBeNull() 128 | expect(node2.next).toBeNull() 129 | expect(list.head).toBe(node1) 130 | expect(list.tail).toBe(node3) 131 | }) 132 | 133 | it('should remove the head node', () => { 134 | // Build list: node1 -> node2 135 | const node1 = new Node(1) 136 | const node2 = new Node(2) 137 | list.push(node1) 138 | list.push(node2) 139 | 140 | list.remove(node1) 141 | 142 | expect(list.head).toBe(node2) 143 | expect(node2.prev).toBeNull() 144 | expect(node1.next).toBeNull() 145 | expect(node1.prev).toBeNull() 146 | }) 147 | 148 | it('should remove the tail node', () => { 149 | // Build list: node1 -> node2 150 | const node1 = new Node(1) 151 | const node2 = new Node(2) 152 | list.push(node1) 153 | list.push(node2) 154 | 155 | list.remove(node2) 156 | 157 | expect(list.tail).toBe(node1) 158 | expect(node1.next).toBeNull() 159 | expect(node2.prev).toBeNull() 160 | expect(node2.next).toBeNull() 161 | }) 162 | }) 163 | 164 | describe('swap', () => { 165 | it('should swap the data of two nodes', () => { 166 | // Build list: node1 -> node2 167 | const node1 = new Node(1) 168 | const node2 = new Node(2) 169 | list.push(node1) 170 | list.push(node2) 171 | 172 | list.swap(node1, node2) 173 | 174 | expect(node1.data).toBe(2) 175 | expect(node2.data).toBe(1) 176 | }) 177 | }) 178 | 179 | describe('fromArray', () => { 180 | it('should create a list from an array of elements', () => { 181 | const array = [1, 2, 3, 4] 182 | const linkedList = new List() 183 | linkedList.fromArray(array) 184 | 185 | const result = [] as number[] 186 | linkedList.map((node) => result.push(node.data)) 187 | 188 | expect(result).toEqual(array) 189 | }) 190 | 191 | it('should handle an empty array', () => { 192 | const array: number[] = [] 193 | const linkedList = new List() 194 | linkedList.fromArray(array) 195 | 196 | expect(linkedList.head).toBeNull() 197 | expect(linkedList.tail).toBeNull() 198 | }) 199 | 200 | it('should correctly set head and tail for a single element array', () => { 201 | const array = [42] 202 | const linkedList = new List() 203 | linkedList.fromArray(array) 204 | 205 | expect(linkedList.head).not.toBeNull() 206 | expect(linkedList.tail).not.toBeNull() 207 | expect(linkedList.head).toBe(linkedList.tail) 208 | expect(linkedList.head?.data).toBe(42) 209 | }) 210 | 211 | it('should correctly link nodes', () => { 212 | const array = [1, 2, 3] 213 | const linkedList = new List() 214 | linkedList.fromArray(array) 215 | 216 | expect(linkedList.head?.data).toBe(1) 217 | expect(linkedList.head?.next?.data).toBe(2) 218 | expect(linkedList.head?.next?.next?.data).toBe(3) 219 | expect(linkedList.head?.next?.next?.next).toBeNull() 220 | 221 | expect(linkedList.tail?.data).toBe(3) 222 | expect(linkedList.tail?.prev?.data).toBe(2) 223 | expect(linkedList.tail?.prev?.prev?.data).toBe(1) 224 | expect(linkedList.tail?.prev?.prev?.prev).toBeNull() 225 | }) 226 | }) 227 | 228 | describe('toArray', () => { 229 | it('should return an array of all list nodes\' data', () => { 230 | const linkedList = new List() 231 | linkedList.push(new Node(1)) 232 | linkedList.push(new Node(2)) 233 | linkedList.push(new Node(3)) 234 | 235 | const result = linkedList.toArray() 236 | 237 | expect(result).toEqual([1, 2, 3]) 238 | }) 239 | 240 | it('should return an empty array when the list is empty', () => { 241 | const linkedList = new List() 242 | 243 | const result = linkedList.toArray() 244 | 245 | expect(result).toEqual([]) 246 | }) 247 | 248 | it('should handle a list with a single node', () => { 249 | const linkedList = new List() 250 | linkedList.push(new Node(42)) 251 | 252 | const result = linkedList.toArray() 253 | 254 | expect(result).toEqual([42]) 255 | }) 256 | 257 | it('should correctly handle a list with multiple nodes', () => { 258 | const linkedList = new List() 259 | linkedList.push(new Node('a')) 260 | linkedList.push(new Node('b')) 261 | linkedList.push(new Node('c')) 262 | 263 | const result = linkedList.toArray() 264 | 265 | expect(result).toEqual(['a', 'b', 'c']) 266 | }) 267 | }) 268 | 269 | describe('toString', () => { 270 | it('should return a concatenated string of JSON stringified node data', 271 | () => { 272 | const node1 = new Node({ value: 1 }) 273 | const node2 = new Node({ value: 2 }) 274 | list.push(node1) 275 | list.push(node2) 276 | 277 | const expectedString = 278 | `${JSON.stringify(node1.data)} ${JSON.stringify(node2.data)}` 279 | expect(list.toString()).toBe(expectedString) 280 | }) 281 | 282 | it('should return an empty string for an empty list', () => { 283 | expect(list.toString()).toBe('') 284 | }) 285 | }) 286 | 287 | describe('map', () => { 288 | it('should iterate over each node and apply the iteratee function', () => { 289 | const node1 = new Node(1) 290 | const node2 = new Node(2) 291 | const node3 = new Node(3) 292 | list.push(node1) 293 | list.push(node2) 294 | list.push(node3) 295 | 296 | const values: number[] = [] 297 | list.map((node) => values.push(node.data)) 298 | 299 | expect(values).toEqual([1, 2, 3]) 300 | }) 301 | }) 302 | }) 303 | -------------------------------------------------------------------------------- /src/data-structures/linkedList.ts: -------------------------------------------------------------------------------- 1 | import Node from './node' 2 | 3 | /** 4 | * Represents a doubly linked list. 5 | * @class 6 | */ 7 | class List { 8 | head: Node | null 9 | tail: Node | null 10 | 11 | /** 12 | * Creates an empty List. 13 | */ 14 | constructor() { 15 | this.head = null 16 | this.tail = null 17 | } 18 | 19 | /** 20 | * Inserts a node after the target node. 21 | * @param {Node} target - The target node to insert after. 22 | * @param {Node} node - The node to insert. 23 | * @returns {Node} The inserted node. 24 | */ 25 | insertAfter(target: Node, node: Node): Node { 26 | if (target.next) { 27 | const nextNode = target.next 28 | node.next = nextNode 29 | node.prev = target 30 | target.next = node 31 | nextNode.prev = node 32 | } else { 33 | this.push(node) 34 | } 35 | 36 | return node 37 | } 38 | 39 | /** 40 | * Inserts a node before the target node. 41 | * @param {Node} target - The target node to insert before. 42 | * @param {Node} node - The node to insert. 43 | * @returns {Node} The inserted node. 44 | */ 45 | insertBefore(target: Node, node: Node): Node { 46 | if (!target.prev) { 47 | node.next = target 48 | target.prev = node 49 | this.head = node 50 | } else { 51 | const prevNode = target.prev 52 | node.prev = prevNode 53 | node.next = target 54 | target.prev = node 55 | prevNode.next = node 56 | } 57 | 58 | return node 59 | } 60 | 61 | /** 62 | * Calls iteratee on each Node of the List. Alters the Node. 63 | * @param {(node: Node) => void} iteratee - The function to call on each node. 64 | */ 65 | map(iteratee: (node: Node) => void): void { 66 | let currentNode = this.head 67 | while (currentNode) { 68 | iteratee(currentNode) 69 | currentNode = currentNode.next 70 | } 71 | } 72 | 73 | /** 74 | * Pushes a Node to the end of the List. 75 | * @param {Node} node - The node to push. 76 | * @returns {Node} The pushed node. 77 | */ 78 | push(node: Node): Node { 79 | if (this.head === null) { 80 | this.head = this.tail = node 81 | } else { 82 | const tailNode = this.tail 83 | if (tailNode) { 84 | tailNode.next = node 85 | node.prev = tailNode 86 | this.tail = node 87 | } 88 | } 89 | 90 | return node 91 | } 92 | 93 | /** 94 | * Removes a Node from the List. 95 | * @param {Node} node - The node to remove. 96 | * @returns {Node} The removed node. 97 | */ 98 | remove(node: Node): Node { 99 | const prevNode = node.prev 100 | const nextNode = node.next 101 | 102 | if (prevNode) { 103 | prevNode.next = nextNode 104 | } else { 105 | this.head = nextNode 106 | } 107 | 108 | if (nextNode) { 109 | nextNode.prev = prevNode 110 | } else { 111 | this.tail = prevNode 112 | } 113 | 114 | node.prev = node.next = null 115 | 116 | return node 117 | } 118 | 119 | /** 120 | * Implements a soft swap of two elements. Actual objects are the same, just 121 | * their 'data' property is swapped. 122 | * @param {Node} left - The first node. 123 | * @param {Node} right - The second node. 124 | */ 125 | swap(left: Node, right: Node): void { 126 | [left.data, right.data] = [right.data, left.data] 127 | } 128 | 129 | /** 130 | * Fills the List with data from an array. 131 | * @param {T[]} array - The array to fill the list with. 132 | */ 133 | fromArray(array: T[]): void { 134 | array.forEach((data) => this.push(new Node(data))) 135 | } 136 | 137 | /** 138 | * @returns {T[]} An array of all list nodes' data. 139 | */ 140 | toArray(): T[] { 141 | const array: T[] = [] 142 | this.map((node) => array.push(node.data)) 143 | return array 144 | } 145 | 146 | /** 147 | * Returns all list nodes' data concatenated into a single string. 148 | * @returns {String} The concatenated string of all nodes' data. 149 | */ 150 | toString(): string { 151 | const items: string[] = [] 152 | this.map((node) => items.push(JSON.stringify(node.data))) 153 | return items.join(' ') 154 | } 155 | } 156 | 157 | export default List -------------------------------------------------------------------------------- /src/data-structures/node.test.ts: -------------------------------------------------------------------------------- 1 | // Node.test.ts 2 | import Node from './node' 3 | 4 | describe('Node', () => { 5 | it('should create a node with correct data and null pointers', () => { 6 | const data = 42 7 | const node = new Node(data) 8 | expect(node.data).toBe(data) 9 | expect(node.next).toBeNull() 10 | expect(node.prev).toBeNull() 11 | }) 12 | 13 | it('should set next node correctly', () => { 14 | const node1 = new Node('first') 15 | const node2 = new Node('second') 16 | node1.setNext(node2) 17 | expect(node1.next).toBe(node2) 18 | }) 19 | 20 | it('should set previous node correctly', () => { 21 | const node1 = new Node('first') 22 | const node2 = new Node('second') 23 | node2.setPrev(node1) 24 | expect(node2.prev).toBe(node1) 25 | }) 26 | 27 | it('should create a node from an object', () => { 28 | const data = { name: 'test' } 29 | const obj = { data } 30 | const node = Node.fromObject(obj) 31 | expect(node.data).toEqual(data) 32 | expect(node.next).toBeNull() 33 | expect(node.prev).toBeNull() 34 | }) 35 | }) -------------------------------------------------------------------------------- /src/data-structures/node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a node in a doubly linked list. 3 | * @class 4 | */ 5 | class Node { 6 | /** 7 | * Creates a Node from an object. 8 | * @param {Object} obj - The object to create a node from. 9 | * @param {T} obj.data - The data to store in the node. 10 | * @returns {Node} The created node. 11 | */ 12 | static fromObject(obj: { data: T }): Node { 13 | return new Node(obj.data) 14 | } 15 | 16 | /** 17 | * @property {T} data - The data stored in the node. 18 | */ 19 | data: T 20 | 21 | /** 22 | * @property {Node | null} next - Pointer to the next node in the list. 23 | */ 24 | next: Node | null 25 | 26 | /** 27 | * @property {Node | null} prev - Pointer to the previous node in the list. 28 | */ 29 | prev: Node | null 30 | 31 | /** 32 | * Creates a Node with two pointers (next and prev) and data. 33 | * @param {T} data - The data to store in the node. 34 | */ 35 | constructor(data: T) { 36 | this.data = data 37 | this.next = null 38 | this.prev = null 39 | } 40 | 41 | /** 42 | * Sets the next node. 43 | * @param {Node | null} nextNode - The next node in the list. 44 | */ 45 | setNext(nextNode: Node | null): void { 46 | this.next = nextNode 47 | } 48 | 49 | /** 50 | * Sets the previous node. 51 | * @param {Node | null} prevNode - The previous node in the list. 52 | */ 53 | setPrev(prevNode: Node | null): void { 54 | this.prev = prevNode 55 | } 56 | } 57 | 58 | export default Node -------------------------------------------------------------------------------- /src/search/sequentialSearch.test.ts: -------------------------------------------------------------------------------- 1 | import List from '../data-structures/linkedList' 2 | import Node from '../data-structures/node' 3 | import sequentialSearch, { Predicate } from './sequentialSearch' 4 | 5 | describe('sequentialSearch', () => { 6 | it('should return null for an empty list', () => { 7 | const list = new List() 8 | const result = sequentialSearch(list, (node) => node.data === 42) 9 | expect(result).toBeNull() 10 | }) 11 | 12 | it('should return the node that matches the predicate', () => { 13 | const list = new List() 14 | const node1 = new Node(10) 15 | const node2 = new Node(20) 16 | const node3 = new Node(30) 17 | list.push(node1) 18 | list.push(node2) 19 | list.push(node3) 20 | 21 | const result = sequentialSearch(list, (node) => node.data === 20) 22 | expect(result).toBe(node2) 23 | }) 24 | 25 | it('should return the first matching node if multiple nodes satisfy the predicate', () => { 26 | const list = new List() 27 | const node1 = new Node('apple') 28 | const node2 = new Node('banana') 29 | const node3 = new Node('apple') 30 | list.push(node1) 31 | list.push(node2) 32 | list.push(node3) 33 | 34 | const result = sequentialSearch(list, (node) => node.data === 'apple') 35 | expect(result).toBe(node1) 36 | }) 37 | 38 | it('should return null if no node satisfies the predicate', () => { 39 | const list = new List() 40 | const node1 = new Node(1) 41 | const node2 = new Node(2) 42 | list.push(node1) 43 | list.push(node2) 44 | 45 | const result = sequentialSearch(list, (node) => node.data === 3) 46 | expect(result).toBeNull() 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /src/search/sequentialSearch.ts: -------------------------------------------------------------------------------- 1 | import List from '../data-structures/linkedList' 2 | import Node from '../data-structures/node' 3 | 4 | /** 5 | * A predicate function that checks whether a given node meets a condition. 6 | * @template T - The type of data stored in the node. 7 | * @param node - The node to evaluate. 8 | * @returns True if the node meets the condition otherwise, false. 9 | */ 10 | export type Predicate = (node: Node) => boolean 11 | 12 | /** 13 | * Performs a sequential search on a linked list. 14 | * 15 | * This function traverses the linked list one node at a time, 16 | * applying the provided predicate to each node. It returns the first node 17 | * for which the predicate returns true. If no node matches, it returns null. 18 | * 19 | * @template T - The type of data stored in the nodes. 20 | * @param list - The linked list to search through. 21 | * @param predicate - A function that determines whether a node satisfies the search condition. 22 | * @returns The first node matching the predicate, or null if no match is found. 23 | */ 24 | function sequentialSearch(list: List, predicate: Predicate): Node | null { 25 | let currentNode = list.head 26 | 27 | while (currentNode) { 28 | if (predicate(currentNode)) { 29 | return currentNode 30 | } 31 | currentNode = currentNode.next 32 | } 33 | 34 | return null 35 | } 36 | 37 | export default sequentialSearch -------------------------------------------------------------------------------- /src/sort/bubbleSort.test.ts: -------------------------------------------------------------------------------- 1 | import List from '../data-structures/linkedList' 2 | import Node from '../data-structures/node' 3 | import bubbleSort from './bubbleSort' 4 | 5 | describe('bubbleSort', () => { 6 | // Error conditions 7 | it('should throw a TypeError if list is not provided', () => { 8 | expect(() => 9 | bubbleSort( 10 | null as any, 11 | (a: Node, b: Node) => a.data > b.data) 12 | ).toThrow(TypeError) 13 | }) 14 | 15 | it('should throw a TypeError if predicate is not provided or not a function', () => { 16 | const list = new List() 17 | list.push(new Node(1)) 18 | expect(() => bubbleSort(list, null as any)).toThrow(TypeError) 19 | }) 20 | 21 | // Sorting numbers 22 | it('should sort a list of two numbers', () => { 23 | const list = new List() 24 | list.push(new Node(2)) 25 | list.push(new Node(1)) 26 | 27 | // Predicate: swap if left.data > right.data 28 | bubbleSort(list, (a, b) => a.data > b.data) 29 | 30 | // Expected list: [1, 2] 31 | const expected = `${JSON.stringify(1)} ${JSON.stringify(2)}` 32 | expect(list.toString()).toBe(expected) 33 | }) 34 | 35 | it('should sort a list of three numbers', () => { 36 | const list = new List() 37 | list.push(new Node(3)) 38 | list.push(new Node(1)) 39 | list.push(new Node(2)) 40 | 41 | bubbleSort(list, (a, b) => a.data > b.data) 42 | 43 | // Expected list: [1, 2, 3] 44 | const expected = `${JSON.stringify(1)} ${JSON.stringify(2)} ${JSON.stringify(3)}` 45 | expect(list.toString()).toBe(expected) 46 | }) 47 | 48 | it('should not change an already sorted list', () => { 49 | const list = new List() 50 | list.push(new Node(1)) 51 | list.push(new Node(2)) 52 | list.push(new Node(3)) 53 | 54 | bubbleSort(list, (a, b) => a.data > b.data) 55 | 56 | const expected = `${JSON.stringify(1)} ${JSON.stringify(2)} ${JSON.stringify(3)}` 57 | expect(list.toString()).toBe(expected) 58 | }) 59 | 60 | it('should handle a single element list', () => { 61 | const list = new List() 62 | list.push(new Node(1)) 63 | 64 | bubbleSort(list, (a, b) => a.data > b.data) 65 | 66 | expect(list.toString()).toBe(JSON.stringify(1)) 67 | }) 68 | 69 | // Sorting strings 70 | it('should sort a list of strings alphabetically', () => { 71 | const list = new List() 72 | list.push(new Node('banana')) 73 | list.push(new Node('apple')) 74 | list.push(new Node('cherry')) 75 | 76 | // Using localeCompare: swap if a.data comes after b.data alphabetically 77 | bubbleSort(list, (a, b) => a.data.localeCompare(b.data) > 0) 78 | 79 | const expected = `${JSON.stringify('apple')} ${JSON.stringify('banana')} ${JSON.stringify('cherry')}` 80 | expect(list.toString()).toBe(expected) 81 | }) 82 | 83 | // Sorting with duplicate values 84 | it('should handle duplicate elements correctly', () => { 85 | const list = new List() 86 | list.push(new Node(2)) 87 | list.push(new Node(1)) 88 | list.push(new Node(2)) 89 | list.push(new Node(1)) 90 | 91 | bubbleSort(list, (a, b) => a.data > b.data) 92 | 93 | // Expected sorted list: [1, 1, 2, 2] 94 | const expected = `${JSON.stringify(1)} ${JSON.stringify(1)} ${JSON.stringify(2)} ${JSON.stringify(2)}` 95 | expect(list.toString()).toBe(expected) 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /src/sort/bubbleSort.ts: -------------------------------------------------------------------------------- 1 | import List from '../data-structures/linkedList' 2 | import Node from '../data-structures/node' 3 | 4 | /** 5 | * Sorts a List using bubble sort algorithm. 6 | * @module sort/Bubble 7 | * @type {Function} 8 | * @param {List} list - The unsorted list. 9 | * @param {(a: Node, b: Node) => boolean} predicate - 10 | * Callback function defining how to compare two elements of the list. 11 | * @throws {TypeError} If list or predicate is not provided. 12 | */ 13 | function bubbleSort(list: List, predicate: (a: Node, b: Node) => boolean): void { 14 | if (!list || typeof predicate !== 'function') { 15 | throw new TypeError('Invalid arguments: list and predicate are required') 16 | } 17 | 18 | let left = list.head 19 | let swapped: boolean 20 | 21 | do { 22 | swapped = false 23 | let right = left 24 | 25 | while (right && right.next) { 26 | if (predicate(right, right.next)) { 27 | // soft swap (only data is swapped, no actual objects) 28 | list.swap(right, right.next) 29 | swapped = true 30 | } 31 | right = right.next 32 | } 33 | 34 | left = left?.next || null 35 | } while (swapped) 36 | } 37 | 38 | export default bubbleSort -------------------------------------------------------------------------------- /src/sort/insertionSort.test.ts: -------------------------------------------------------------------------------- 1 | import List from '../data-structures/linkedList' 2 | import Node from '../data-structures/node' 3 | import insertionSort from './insertionSort' 4 | 5 | describe('insertionSort', () => { 6 | // Error conditions 7 | it('should throw a TypeError if list is not provided', () => { 8 | expect(() => insertionSort( 9 | null as any, 10 | (a: Node, b: Node) => a.data > b.data) 11 | ).toThrow(TypeError) 12 | }) 13 | 14 | it('should throw a TypeError if predicate is not a function', () => { 15 | const list = new List() 16 | list.push(new Node(1)) 17 | expect(() => insertionSort(list, null as any)).toThrow(TypeError) 18 | }) 19 | 20 | // Sorting numbers 21 | it('should sort a list of numbers', () => { 22 | const list = new List() 23 | list.push(new Node(4)) 24 | list.push(new Node(2)) 25 | list.push(new Node(5)) 26 | list.push(new Node(1)) 27 | list.push(new Node(3)) 28 | 29 | // Predicate: swap if left.data > right.data (ascending order) 30 | insertionSort(list, (a, b) => a.data > b.data) 31 | 32 | // Expected order: 1, 2, 3, 4, 5 33 | expect(list.toString()).toBe('1 2 3 4 5') 34 | }) 35 | 36 | it('should handle an already sorted list', () => { 37 | const list = new List() 38 | list.push(new Node(1)) 39 | list.push(new Node(2)) 40 | list.push(new Node(3)) 41 | 42 | insertionSort(list, (a, b) => a.data > b.data) 43 | 44 | // Expected order: 1, 2, 3 45 | expect(list.toString()).toBe('1 2 3') 46 | }) 47 | 48 | it('should handle a single-element list', () => { 49 | const list = new List() 50 | list.push(new Node(42)) 51 | 52 | insertionSort(list, (a, b) => a.data > b.data) 53 | expect(list.toString()).toBe(JSON.stringify(42)) 54 | }) 55 | 56 | // Sorting strings 57 | it('should sort a list of strings alphabetically', () => { 58 | const list = new List() 59 | list.push(new Node('banana')) 60 | list.push(new Node('apple')) 61 | list.push(new Node('cherry')) 62 | 63 | // Using localeCompare to determine alphabetical order. 64 | insertionSort(list, (a, b) => a.data.localeCompare(b.data) > 0) 65 | const expected = `${JSON.stringify('apple')} ${JSON.stringify('banana')} ${JSON.stringify('cherry')}` 66 | expect(list.toString()).toBe(expected) 67 | }) 68 | 69 | // Sorting with duplicate values 70 | it('should handle duplicate elements correctly', () => { 71 | const list = new List() 72 | list.push(new Node(3)) 73 | list.push(new Node(1)) 74 | list.push(new Node(2)) 75 | list.push(new Node(1)) 76 | list.push(new Node(3)) 77 | 78 | insertionSort(list, (a, b) => a.data > b.data) 79 | 80 | // Expected sorted order: 1, 1, 2, 3, 3 81 | expect(list.toString()).toBe('1 1 2 3 3') 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /src/sort/insertionSort.ts: -------------------------------------------------------------------------------- 1 | import List from '../data-structures/linkedList' 2 | import Node from '../data-structures/node' 3 | 4 | /** 5 | * Sorts a linked list using the insertion sort algorithm. 6 | * The algorithm iterates over the list, and for each node (starting from the second), 7 | * finds the correct sorted position in the already-sorted portion (to its left) and 8 | * re-inserts the node there. 9 | * 10 | * @param list - The linked list to sort. 11 | * @param predicate - 12 | * A comparison function that returns true if the first node should come after the second. 13 | * For example, (a, b) => a.data > b.data will sort numbers in ascending order. 14 | * @throws {TypeError} If the list or predicate is not provided or invalid. 15 | */ 16 | function insertionSort( 17 | list: List, 18 | predicate: (a: Node, b: Node) => boolean 19 | ): void { 20 | if (!list || typeof predicate !== 'function') { 21 | throw new TypeError('Invalid arguments: list and predicate are required') 22 | } 23 | 24 | // Start from the second node because the first node is already "sorted" 25 | let current: Node | null = list.head ? list.head.next : null 26 | 27 | while (current) { 28 | // Save a reference to the next node, as current may be moved 29 | const nextCurrent = current.next 30 | const keyNode = current 31 | 32 | // Find the proper position in the sorted portion (nodes before keyNode) 33 | let sortedPosition: Node | null = keyNode.prev 34 | while (sortedPosition && predicate(sortedPosition, keyNode)) { 35 | sortedPosition = sortedPosition.prev 36 | } 37 | 38 | // If the node is not already in the correct spot, reposition it. 39 | // (keyNode.prev !== sortedPosition) indicates that keyNode must be moved. 40 | if (keyNode.prev !== sortedPosition) { 41 | // Remove keyNode from its current position. 42 | list.remove(keyNode) 43 | 44 | if (sortedPosition === null) { 45 | // Insert at the beginning: keyNode becomes the new head. 46 | if (list.head) { 47 | list.insertBefore(list.head, keyNode) 48 | } else { 49 | // Fallback: if list is empty, push keyNode (should not happen since keyNode was in list) 50 | list.push(keyNode) 51 | } 52 | } else { 53 | // Otherwise, insert keyNode right after sortedPosition. 54 | list.insertAfter(sortedPosition, keyNode) 55 | } 56 | } 57 | 58 | // Continue with the next node in the original order. 59 | current = nextCurrent 60 | } 61 | } 62 | 63 | export default insertionSort -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "outDir": "./dist" 10 | }, 11 | "include": ["src/**/*.ts"], 12 | "exclude": ["node_modules"] 13 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended", 5 | "tslint-config-prettier" 6 | ], 7 | "jsRules": {}, 8 | "rules": { 9 | "semicolon": [true, "never"], 10 | "max-line-length": [true, 100], 11 | "trailing-comma": [false], 12 | "quotemark": [true, "single"], 13 | "no-console": false, 14 | "object-literal-sort-keys": false, 15 | "interface-name": false, 16 | "member-access": false, 17 | "ordered-imports": false 18 | }, 19 | "rulesDirectory": [] 20 | } -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "entryPoints": ["./src/**/*.ts"], 4 | "exclude": ["./src/**/*.test.ts"], 5 | "out": "docs" 6 | } --------------------------------------------------------------------------------