Skip to content

Commit 22c7c0f

Browse files
rfearingRicardo Fearing
authored andcommitted
Add SinglyLinkedList Data Structure Class (#1)
* Add SinglyLinkedList with push method * Add Pop to SinglyLinkedList * Add lessons learned to README * Add todo methods and snub tests * Add shift method to SinglyLinkedList * Add unshift method to SinglyLinkedList class * Add get method to SinglyLinkedList * Add link to SinglyLinkedList README * Add set method to SinglyLinkedList * Add insert and remove methods to SinglyLinkedList * Add reverse method to SLL * Make methods private and remove optional chaining * Update method names * Update tests * Add list v array to README * Update class to use TS private modifier * Update tests to utilize beforeEach
1 parent a4e16ce commit 22c7c0f

File tree

12 files changed

+419
-12
lines changed

12 files changed

+419
-12
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ If you are looking for a location to learn data structures in JavaScript, [this
1515
Each Data Structure will have it's own README describing the data structure. Hopefully, this list will continue to grow.
1616

1717
**Data Structures:**
18-
- [ ] Singly Linked List
18+
- [Singly Linked List](src/singlyLinkedList/README.md)
1919
- [ ] Double Linked List
2020
- [ ] Stack
2121
- [ ] Queue
@@ -24,7 +24,9 @@ Each Data Structure will have it's own README describing the data structure. Hop
2424
- [ ] Hash Table
2525
- [ ] Graph
2626

27+
I'm utilizing the [Wiki page as a "lessons learned" section][lessons-learned]. Hopefully it'll help future me, and whoever stumbles upon this repo.
28+
2729
<!-- Links -->
2830
[datastructures-js]:https://datastructures-js.github.io/
2931
[js-algo]:https://github.com/trekhleb/javascript-algorithms
30-
32+
[lessons-learned]:https://github.com/rfearing/js-data-structures/wiki/What-I-learned-along-the-way
81.9 KB
Loading

assets/singly-linked-list.png

44.3 KB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rfearing/js-data-structures",
3-
"version": "0.1.0",
3+
"version": "0.1.2",
44
"description": "Examples of Data Structures in JavaScript",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const Greeter = (name: string) => `Hello ${name}`;
1+
export { SinglyLinkedList } from './singlyLinkedList';

src/singlyLinkedList/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Singly Linked List
2+
3+
From [geeksongeeks.com][geeks]:
4+
5+
> [A singly linked list] is the simplest type of linked list in which every node contains some data and a pointer to the next node of the same data type. The node contains a pointer to the next node means that the node stores the address of the next node in the sequence. A single linked list allows traversal of data only in one way
6+
7+
<img src="../../assets/singly-linked-list.png" width="300" alt="Graph representation of a singly linked list" />
8+
9+
### Singly Linked List vs Array
10+
11+
| Lists | Array |
12+
|---------------------------------------------|---------------------------------------------|
13+
| Do not have indexes | Indexed in order |
14+
| Connected via nodes with a **next** pointer | Insertion &amp; deletion can be expensive |
15+
| Random access is not allowed | Can quickly be accessed at a specific index |
16+
17+
Singly linked list is preferred when we need to save memory and searching is not required as pointer of single index is stored.
18+
19+
[geeks]: https://www.geeksforgeeks.org/types-of-linked-list/#:~:text=Singly%20Linked%20List%3A%20It%20is,next%20node%20in%20the%20sequence.

src/singlyLinkedList/index.ts

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import { SinglyLinkedNode as Node } from './node';
2+
import assert from 'assert';
3+
4+
export class SinglyLinkedList<T> {
5+
constructor() {
6+
this.head = null;
7+
this.tail = null;
8+
this.length = 0;
9+
}
10+
11+
private head: Node<T> | null;
12+
private tail: Node<T> | null;
13+
private length: number;
14+
15+
/**
16+
* Return whether or not the SinglyLinkedList has any nodes.
17+
* @returns {boolean}
18+
*/
19+
isEmpty() {
20+
return this.length === 0;
21+
}
22+
23+
/**
24+
* Get a node at a given position.
25+
* @param {number} index - Where the node is located in the list.
26+
* @returns {SinglyLinkedNode | null}
27+
*/
28+
private getNode(index: number) {
29+
if (this.isEmpty() || index >= this.length || index < 0) {
30+
return null;
31+
}
32+
let current: Node<T> | null = this.head;
33+
// If index is 0, we'll never loop
34+
for (let i = 0; i < index; i++) {
35+
assert(current); // We are sure we're never out of bounds
36+
current = current.getNext();
37+
}
38+
return current;
39+
}
40+
41+
/**
42+
* Get length of the list.
43+
* @returns {number}
44+
*/
45+
getLength() {
46+
return this.length;
47+
}
48+
49+
/**
50+
* Insert a node at the end of the list
51+
* @param {T} val
52+
* @returns {SinglyLinkedNode}
53+
*/
54+
insertLast(val: T) {
55+
const newNode = new Node(val);
56+
if (this.isEmpty()) {
57+
this.head = newNode;
58+
this.tail = this.head;
59+
} else {
60+
assert(this.tail);
61+
this.tail.setNext(newNode);
62+
this.tail = newNode;
63+
}
64+
this.length++;
65+
return this.head;
66+
}
67+
68+
/**
69+
* Remove a node from the end of the list
70+
* @returns {SinglyLinkedNode} The removed node
71+
*/
72+
removeLast() {
73+
if (this.isEmpty()) {
74+
return null;
75+
}
76+
assert(this.head);
77+
78+
let newTail = this.head;
79+
let current: Node<T> | null = this.head;
80+
let persist = true;
81+
82+
// We want to get to the second to last node:
83+
while (persist) {
84+
assert(current);
85+
newTail = current;
86+
if (current.getNext()) {
87+
current = current.getNext();
88+
} else {
89+
// If getNext is null. We can safely exit.
90+
persist = false;
91+
}
92+
}
93+
94+
// and then set it to the new tail:
95+
newTail.setNext();
96+
this.tail = newTail;
97+
this.length--;
98+
99+
// Reset if popped the last node.
100+
if (this.isEmpty()) {
101+
this.head = null;
102+
this.tail = null;
103+
}
104+
105+
return current;
106+
}
107+
108+
/**
109+
* Remove a node from the beginning of the list.
110+
* returns {SinglyLinkedNode} The removed node
111+
*/
112+
removeFirst() {
113+
if (this.isEmpty()) {
114+
return null;
115+
}
116+
assert(this.head);
117+
const oldHead = this.head;
118+
this.head = oldHead.getNext() || null;
119+
this.length--;
120+
121+
// Unlink the old head.
122+
oldHead.setNext();
123+
124+
// Reset tail if shifted the last node.
125+
if (this.isEmpty()) {
126+
this.tail = null;
127+
}
128+
return oldHead;
129+
}
130+
131+
/**
132+
* Add a node to the beginning of the list.
133+
* @param {T} val
134+
* @returns {SinglyLinkedNode}
135+
*/
136+
insertFirst(value: T) {
137+
const newNode = new Node(value);
138+
// Tail and head are the newNode if empty.
139+
if (this.isEmpty()) {
140+
this.head = newNode;
141+
this.tail = this.head;
142+
}
143+
// Set newNode next to the current head's next
144+
newNode.setNext(this.head);
145+
// Then set as the new head
146+
this.head = newNode;
147+
this.length++;
148+
return this.head;
149+
}
150+
151+
/**
152+
* Get a value from a node at a given position.
153+
* @param {number} index - Where the node is located in the list.
154+
* @returns {T} - the value passed into the SinglyLinkedNode constructor
155+
*/
156+
get(index: number) {
157+
const searchedNode = this.getNode(index);
158+
return searchedNode ? searchedNode.getValue() : null;
159+
}
160+
161+
/**
162+
* Set a node at the current position.
163+
* @param {T} value - Value to replace at index.
164+
* @param {number} index - Where the node is located in the list
165+
* @returns {SinglyLinkedNode | null}
166+
*/
167+
set(value: T, index: number) {
168+
// We can utilize the logic from our get method
169+
const foundNode = this.getNode(index);
170+
if (!foundNode) {
171+
return null;
172+
}
173+
foundNode.setValue(value);
174+
return foundNode;
175+
}
176+
177+
/**
178+
* Insert a new node at the specified position
179+
* @param {T} value - Value for the new node to place at index
180+
* @param {number} index - Where the node is located in the list
181+
*/
182+
insert(value: T, index: number) {
183+
if (index > this.length || index < 0) {
184+
throw new Error('The index was outside of the List');
185+
}
186+
187+
if (index === 0) {
188+
return !!this.unshift(value);
189+
}
190+
191+
if (index === this.length) {
192+
return !!this.push(value);
193+
}
194+
195+
const newNode = new Node(value);
196+
const previous = this.getNode(index - 1);
197+
if (previous) {
198+
newNode.setNext(previous.getNext());
199+
previous.setNext(newNode);
200+
}
201+
202+
this.length++;
203+
return true;
204+
}
205+
206+
/**
207+
* Remove a node from the list at a given index.
208+
* @param index - The index of the node we want to remove.
209+
* @returns {SinglyLinkedNode | null} - The removed node.
210+
*/
211+
remove(index: number) {
212+
// Get head, tail cases first:
213+
if (index === 0) {
214+
return this.shift();
215+
}
216+
if (index === this.length - 1) {
217+
return this.pop();
218+
}
219+
220+
const previous = this.getNode(index - 1);
221+
assert(previous);
222+
223+
const removed = previous.getNext();
224+
assert(removed);
225+
226+
previous.setNext(removed.getNext());
227+
this.length--;
228+
return removed;
229+
}
230+
231+
/**
232+
* Reverse the list to point in the opposite direction.
233+
* Note, this does not really have a real-world use-case.
234+
* Other than to learn, this could be asked, in some contexts, as an interview question.
235+
* @returns {SinglyLinkedList<T>}
236+
*/
237+
reverse() {
238+
// Swap head for tail:
239+
let node = this.head;
240+
this.head = this.tail;
241+
this.tail = node;
242+
let next;
243+
let previous = null;
244+
for (let i = 0; i < this.length; i++) {
245+
assert(node);
246+
next = node.getNext(); // First iteration, this will be this.head.next
247+
node.setNext(previous); // First iteration, will set next to null, as it's the new tail.
248+
previous = node;
249+
node = next;
250+
}
251+
return null;
252+
}
253+
254+
// Aliases
255+
push = this.insertLast;
256+
pop = this.removeLast;
257+
shift = this.removeFirst;
258+
unshift = this.insertFirst;
259+
}

src/singlyLinkedList/node.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export class SinglyLinkedNode<T> {
2+
constructor(value: T) {
3+
this.value = value;
4+
this.next = null;
5+
}
6+
7+
private next: SinglyLinkedNode<T> | null;
8+
private value: T | null;
9+
10+
setValue(value: T) {
11+
this.value = value;
12+
}
13+
14+
getValue() {
15+
return this.value;
16+
}
17+
18+
setNext(next?: SinglyLinkedNode<T> | null) {
19+
if (next && !(next instanceof SinglyLinkedNode)) {
20+
throw new Error('setNext expects a SinglyLinkedNode or null');
21+
}
22+
this.next = next || null;
23+
}
24+
25+
getNext() {
26+
return this.next;
27+
}
28+
29+
hasNext() {
30+
return !!this.next;
31+
}
32+
}

tests/Greeter.test.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)