Skip to content

Commit 4c5469f

Browse files
authored
feat: stack with linked list (#110)
* feat: singly linked list * feat: stack with linked list * feat: combine test cases * fix: tests now work properly
1 parent 1ff8f20 commit 4c5469f

8 files changed

+537
-111
lines changed

data_structures/doubly_linked_list.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { LinkedList } from "./linked_list";
2+
13
/**
24
* This is an implementation of a Doubly Linked List.
35
* A Doubly Linked List is a data structure that contains a head, tail and length property.
@@ -10,7 +12,7 @@
1012
* @property tail The tail of the list.
1113
* @property length The length of the list.
1214
*/
13-
export class DoublyLinkedList<T> {
15+
export class DoublyLinkedList<T> implements LinkedList<T> {
1416
private head?: DoublyLinkedListNode<T> = undefined;
1517
private tail?: DoublyLinkedListNode<T> = undefined;
1618
private length: number = 0;

data_structures/linked_list.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* An interface for linked lists, which shares the common methods.
3+
*/
4+
export interface LinkedList<T> {
5+
isEmpty(): boolean;
6+
get(index: number): T | null | undefined;
7+
push(data: T): void;
8+
pop(): T | undefined;
9+
append(data: T): void;
10+
removeTail(): T | undefined;
11+
insertAt(index: number, data: T): void;
12+
removeAt(index: number): T | undefined;
13+
clear(): void;
14+
toArray(): (T | undefined)[];
15+
getLength(): number;
16+
}

data_structures/linked_list_stack.ts

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { SinglyLinkedList } from "./singly_linked_list";
2+
3+
/**
4+
* This is an implementation of a stack, based on a linked list.
5+
* A stack is a linear data structure that works with the LIFO (Last-In-First-Out) principle.
6+
* A linked list is a linear data structure that works with the FIFO (First-In-First-Out) principle and uses references
7+
* to determine which element is next in the list.
8+
*/
9+
export class LinkedListStack<T> {
10+
private list: SinglyLinkedList<T>;
11+
private limit: number;
12+
13+
/**
14+
* Creates a new stack object.
15+
*/
16+
constructor(limit: number = Number.MAX_VALUE) {
17+
this.list = new SinglyLinkedList<T>();
18+
this.limit = limit;
19+
}
20+
21+
/**
22+
* Gets the top element of the stack.
23+
* Time complexity: constant (O(1))
24+
*
25+
* @returns The top element of the stack.
26+
*/
27+
top(): T | null {
28+
if (this.list.isEmpty()) {
29+
return null;
30+
}
31+
32+
return this.list.get(0)!;
33+
}
34+
35+
/**
36+
* Inserts a new element on the top of the stack.
37+
* Time complexity: constant (O(1))
38+
*
39+
* @param data The data of the element to insert.
40+
* @throws Stack overflow, if the new element does not fit in the limit.
41+
*/
42+
push(data: T): void {
43+
if (this.list.getLength() + 1 > this.limit) {
44+
throw new Error('Stack overflow')
45+
}
46+
47+
this.list.push(data);
48+
}
49+
50+
/**
51+
* Removes the top element from the stack.
52+
* Time complexity: constant (O(1))
53+
*
54+
* @returns The previous top element.
55+
* @throws Stack underflow, if the stack has no elements to pop.
56+
*/
57+
pop(): T {
58+
if (this.list.isEmpty()) {
59+
throw new Error('Stack underflow')
60+
}
61+
62+
return this.list.pop();
63+
}
64+
65+
/**
66+
* Gets the amount of elements in the stack.
67+
*
68+
* @returns The amount of elements in the stack.
69+
*/
70+
length(): number {
71+
return this.list.getLength();
72+
}
73+
74+
/**
75+
* Gets whether the stack is empty or not.
76+
*
77+
* @returns Whether the stack is empty or not.
78+
*/
79+
isEmpty(): boolean {
80+
return this.list.isEmpty();
81+
}
82+
}

data_structures/singly_linked_list.ts

+275
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
import { LinkedList } from "./linked_list";
2+
3+
/**
4+
* Represents a node in a linked list.
5+
*
6+
* @template T The type of the data stored in the node.
7+
* @property data The data stored in the node.
8+
* @property next A reference to the next node in the list. Can reference to null, if there is no next element.
9+
*/
10+
class ListNode<T> {
11+
constructor(public data: T, public next?: ListNode<T>) {}
12+
}
13+
14+
/**
15+
* This is an implementation of a (singly) linked list.
16+
* A linked list is a data structure that stores each element with a pointer (or reference) to the next element
17+
* in the list. Therefore, it is a linear data structure, which can be resized dynamically during runtime, as there is
18+
* no fixed memory block allocated.
19+
*
20+
* @template T The type of the value of the nodes.
21+
* @property head The head of the list.
22+
* @property tail The tail of the list.
23+
* @property length The length of the list.
24+
*/
25+
export class SinglyLinkedList<T> implements LinkedList<T> {
26+
private head?: ListNode<T>;
27+
private tail?: ListNode<T>;
28+
private length: number;
29+
30+
/**
31+
* Creates a new, empty linked list.
32+
*/
33+
constructor() {
34+
this.head = undefined;
35+
this.tail = undefined;
36+
this.length = 0;
37+
}
38+
39+
/**
40+
* Checks, if the list is empty.
41+
*
42+
* @returns Whether the list is empty or not.
43+
*/
44+
isEmpty(): boolean {
45+
return !this.head;
46+
}
47+
48+
/**
49+
* Gets the data of the node at the given index.
50+
* Time complexity: linear (O(n))
51+
*
52+
* @param index The index of the node.
53+
* @returns The data of the node at the given index or null, if no data is present.
54+
*/
55+
get(index: number): T | null {
56+
if (index < 0 || index >= this.length) {
57+
return null;
58+
}
59+
60+
if (this.isEmpty()) {
61+
return null;
62+
}
63+
64+
let currentNode: ListNode<T> = this.head!;
65+
for (let i: number = 0; i < index; i++) {
66+
if (!currentNode.next) {
67+
return null;
68+
}
69+
70+
currentNode = currentNode.next;
71+
}
72+
73+
return currentNode.data;
74+
}
75+
76+
/**
77+
* Inserts the given data as the first node of the list.
78+
* Time complexity: constant (O(1))
79+
*
80+
* @param data The data to be inserted.
81+
*/
82+
push(data: T): void {
83+
const node: ListNode<T> = new ListNode<T>(data);
84+
85+
if (this.isEmpty()) {
86+
this.head = node;
87+
this.tail = node;
88+
} else {
89+
node.next = this.head;
90+
this.head = node;
91+
}
92+
93+
this.length++;
94+
}
95+
96+
/**
97+
* Removes the first node of the list.
98+
* Time complexity: constant (O(1))
99+
*
100+
* @returns The data of the node that was removed.
101+
* @throws Index out of bounds if the list is empty.
102+
*/
103+
pop(): T {
104+
if (this.isEmpty()) {
105+
throw new Error('Index out of bounds');
106+
}
107+
108+
const node: ListNode<T> = this.head!;
109+
this.head = this.head!.next;
110+
this.length--;
111+
112+
return node.data;
113+
}
114+
115+
/**
116+
* Inserts the given data as a new node after the current TAIL.
117+
* Time complexity: constant (O(1))
118+
*
119+
* @param data The data of the node being inserted.
120+
*/
121+
append(data: T): void {
122+
const node: ListNode<T> = new ListNode<T>(data);
123+
124+
if (this.isEmpty()) {
125+
this.head = node;
126+
} else {
127+
this.tail!.next = node;
128+
}
129+
130+
this.tail = node;
131+
this.length++;
132+
}
133+
134+
/**
135+
* Removes the current TAIL of the list.
136+
* Time complexity: linear (O(n))
137+
*
138+
* @returns The data of the former TAIL.
139+
* @throws Index out of bounds if the list is empty.
140+
*/
141+
removeTail(): T {
142+
if (!this.head) {
143+
throw new Error('Index out of bounds');
144+
}
145+
146+
const currentTail = this.tail;
147+
if (this.head === this.tail) {
148+
this.head = undefined;
149+
this.tail = undefined;
150+
this.length--;
151+
152+
return currentTail!.data;
153+
}
154+
155+
let currentNode: ListNode<T> = this.head;
156+
while (currentNode.next !== currentTail) {
157+
currentNode = currentNode.next!;
158+
}
159+
160+
this.tail = currentNode;
161+
this.length--;
162+
163+
return currentTail!.data;
164+
}
165+
166+
/**
167+
* Inserts the data as a new node at the given index.
168+
* Time complexity: O(n)
169+
*
170+
* @param index The index where the node is to be inserted.
171+
* @param data The data to insert.
172+
* @throws Index out of bounds, when given an invalid index.
173+
*/
174+
insertAt(index: number, data: T): void {
175+
if (index < 0 || index > this.length) {
176+
throw new Error('Index out of bounds');
177+
}
178+
179+
if (index === 0) {
180+
this.push(data);
181+
182+
return;
183+
}
184+
185+
if (index === this.length) {
186+
this.append(data);
187+
188+
return;
189+
}
190+
191+
const newNode = new ListNode<T>(data);
192+
let currentNode: ListNode<T> | undefined = this.head;
193+
for (let i: number = 0; i < index - 1; i++) {
194+
currentNode = currentNode?.next;
195+
}
196+
197+
const nextNode = currentNode?.next;
198+
currentNode!.next = newNode;
199+
newNode.next = nextNode;
200+
201+
this.length++;
202+
}
203+
204+
/**
205+
* Removes the node at the given index.
206+
* Time complexity: O(n)
207+
*
208+
* @param index The index of the node to be removed.
209+
* @returns The data of the removed node.
210+
* @throws Index out of bounds, when given an invalid index.
211+
*/
212+
removeAt(index: number): T {
213+
if (index < 0 || index >= this.length) {
214+
throw new Error('Index out of bounds');
215+
}
216+
217+
if (index === 0) {
218+
return this.pop();
219+
}
220+
221+
if (index === this.length - 1) {
222+
return this.removeTail();
223+
}
224+
225+
let previousNode: ListNode<T> | undefined;
226+
let currentNode: ListNode<T> | undefined = this.head;
227+
for (let i: number = 0; i < index; i++) {
228+
if (i === index - 1) {
229+
previousNode = currentNode;
230+
}
231+
232+
currentNode = currentNode?.next;
233+
}
234+
235+
previousNode!.next = currentNode?.next;
236+
this.length--;
237+
238+
return currentNode!.data;
239+
}
240+
241+
/**
242+
* Clears the list.
243+
*/
244+
clear(): void {
245+
this.head = undefined;
246+
this.tail = undefined;
247+
this.length = 0;
248+
}
249+
250+
/**
251+
* Converts the list to an array.
252+
*
253+
* @returns The array representation of the list.
254+
*/
255+
toArray(): T[] {
256+
const array: T[] = [];
257+
let currentNode: ListNode<T> | undefined = this.head;
258+
259+
while (currentNode) {
260+
array.push(currentNode.data);
261+
currentNode = currentNode.next;
262+
}
263+
264+
return array;
265+
}
266+
267+
/**
268+
* Gets the length of the list.
269+
*
270+
* @returns The length of the list.
271+
*/
272+
getLength(): number {
273+
return this.length;
274+
}
275+
}

0 commit comments

Comments
 (0)