├── .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 | 
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 = `M M N E P V F C I C P M F P C P T T A A A T R `;
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 | M M N E P V F C I C P M F P C P T T A A A T R
--------------------------------------------------------------------------------
/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 Constructors Properties Methodsfrom Array fromArray ( array : T [] ) : void map map ( iteratee : ( node : default < T > ) => void ) : void to Array Returns T [] An array of all list nodes' data.
38 |
to String Returns string The concatenated string of all nodes' data.
40 |
41 |
--------------------------------------------------------------------------------
/docs/classes/data-structures_node.default.html:
--------------------------------------------------------------------------------
1 | default | Algorithms Constructors Properties Methodsset Next setNext ( nextNode : null | default < T > ) : void set Prev setPrev ( prevNode : null | default < T > ) : void
20 |
--------------------------------------------------------------------------------
/docs/functions/search_sequentialSearch.default.html:
--------------------------------------------------------------------------------
1 | default | Algorithms 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
10 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | Algorithms
Algorithms
2 |
Algorithms 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 |
API Reference Find the API here
5 |
Book! The book is getting there… the infographics are on github
6 |
7 |
Search
14 |
Order statistics
17 |
18 |
--------------------------------------------------------------------------------
/docs/modules.html:
--------------------------------------------------------------------------------
1 | Algorithms
2 |
--------------------------------------------------------------------------------
/docs/modules/data-structures_linkedList.html:
--------------------------------------------------------------------------------
1 | data-structures/linkedList | Algorithms Module data-structures/linkedList Classesdefault
2 |
--------------------------------------------------------------------------------
/docs/modules/data-structures_node.html:
--------------------------------------------------------------------------------
1 | data-structures/node | Algorithms Module data-structures/node Classesdefault
2 |
--------------------------------------------------------------------------------
/docs/modules/search_sequentialSearch.html:
--------------------------------------------------------------------------------
1 | search/sequentialSearch | Algorithms Module search/sequentialSearch Type AliasesPredicate Functionsdefault
2 |
--------------------------------------------------------------------------------
/docs/modules/sort_bubbleSort.html:
--------------------------------------------------------------------------------
1 | sort/bubbleSort | Algorithms
2 |
--------------------------------------------------------------------------------
/docs/modules/sort_insertionSort.html:
--------------------------------------------------------------------------------
1 | sort/insertionSort | Algorithms Module sort/insertionSort Functionsdefault
2 |
--------------------------------------------------------------------------------
/docs/types/search_sequentialSearch.Predicate.html:
--------------------------------------------------------------------------------
1 | Predicate | Algorithms Predicate : ( node : default < T > ) => boolean
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 | }
--------------------------------------------------------------------------------
Represents a doubly linked list.
2 |