Skip to content

feat: stack with linked list #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion data_structures/doubly_linked_list.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LinkedList } from "./linked_list";

/**
* This is an implementation of a Doubly Linked List.
* A Doubly Linked List is a data structure that contains a head, tail and length property.
Expand All @@ -10,7 +12,7 @@
* @property tail The tail of the list.
* @property length The length of the list.
*/
export class DoublyLinkedList<T> {
export class DoublyLinkedList<T> implements LinkedList<T> {
private head?: DoublyLinkedListNode<T> = undefined;
private tail?: DoublyLinkedListNode<T> = undefined;
private length: number = 0;
Expand Down
16 changes: 16 additions & 0 deletions data_structures/linked_list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* An interface for linked lists, which shares the common methods.
*/
export interface LinkedList<T> {
isEmpty(): boolean;
get(index: number): T | null | undefined;
push(data: T): void;
pop(): T | undefined;
append(data: T): void;
removeTail(): T | undefined;
insertAt(index: number, data: T): void;
removeAt(index: number): T | undefined;
clear(): void;
toArray(): (T | undefined)[];
getLength(): number;
}
82 changes: 82 additions & 0 deletions data_structures/linked_list_stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { SinglyLinkedList } from "./singly_linked_list";

/**
* This is an implementation of a stack, based on a linked list.
* A stack is a linear data structure that works with the LIFO (Last-In-First-Out) principle.
* A linked list is a linear data structure that works with the FIFO (First-In-First-Out) principle and uses references
* to determine which element is next in the list.
*/
export class LinkedListStack<T> {
private list: SinglyLinkedList<T>;
private limit: number;

/**
* Creates a new stack object.
*/
constructor(limit: number = Number.MAX_VALUE) {
this.list = new SinglyLinkedList<T>();
this.limit = limit;
}

/**
* Gets the top element of the stack.
* Time complexity: constant (O(1))
*
* @returns The top element of the stack.
*/
top(): T | null {
if (this.list.isEmpty()) {
return null;
}

return this.list.get(0)!;
}

/**
* Inserts a new element on the top of the stack.
* Time complexity: constant (O(1))
*
* @param data The data of the element to insert.
* @throws Stack overflow, if the new element does not fit in the limit.
*/
push(data: T): void {
if (this.list.getLength() + 1 > this.limit) {
throw new Error('Stack overflow')
}

this.list.push(data);
}

/**
* Removes the top element from the stack.
* Time complexity: constant (O(1))
*
* @returns The previous top element.
* @throws Stack underflow, if the stack has no elements to pop.
*/
pop(): T {
if (this.list.isEmpty()) {
throw new Error('Stack underflow')
}

return this.list.pop();
}

/**
* Gets the amount of elements in the stack.
*
* @returns The amount of elements in the stack.
*/
length(): number {
return this.list.getLength();
}

/**
* Gets whether the stack is empty or not.
*
* @returns Whether the stack is empty or not.
*/
isEmpty(): boolean {
return this.list.isEmpty();
}
}
275 changes: 275 additions & 0 deletions data_structures/singly_linked_list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import { LinkedList } from "./linked_list";

/**
* Represents a node in a linked list.
*
* @template T The type of the data stored in the node.
* @property data The data stored in the node.
* @property next A reference to the next node in the list. Can reference to null, if there is no next element.
*/
class ListNode<T> {
constructor(public data: T, public next?: ListNode<T>) {}
}

/**
* This is an implementation of a (singly) linked list.
* A linked list is a data structure that stores each element with a pointer (or reference) to the next element
* in the list. Therefore, it is a linear data structure, which can be resized dynamically during runtime, as there is
* no fixed memory block allocated.
*
* @template T The type of the value of the nodes.
* @property head The head of the list.
* @property tail The tail of the list.
* @property length The length of the list.
*/
export class SinglyLinkedList<T> implements LinkedList<T> {
private head?: ListNode<T>;
private tail?: ListNode<T>;
private length: number;

/**
* Creates a new, empty linked list.
*/
constructor() {
this.head = undefined;
this.tail = undefined;
this.length = 0;
}

/**
* Checks, if the list is empty.
*
* @returns Whether the list is empty or not.
*/
isEmpty(): boolean {
return !this.head;
}

/**
* Gets the data of the node at the given index.
* Time complexity: linear (O(n))
*
* @param index The index of the node.
* @returns The data of the node at the given index or null, if no data is present.
*/
get(index: number): T | null {
if (index < 0 || index >= this.length) {
return null;
}

if (this.isEmpty()) {
return null;
}

let currentNode: ListNode<T> = this.head!;
for (let i: number = 0; i < index; i++) {
if (!currentNode.next) {
return null;
}

currentNode = currentNode.next;
}

return currentNode.data;
}

/**
* Inserts the given data as the first node of the list.
* Time complexity: constant (O(1))
*
* @param data The data to be inserted.
*/
push(data: T): void {
const node: ListNode<T> = new ListNode<T>(data);

if (this.isEmpty()) {
this.head = node;
this.tail = node;
} else {
node.next = this.head;
this.head = node;
}

this.length++;
}

/**
* Removes the first node of the list.
* Time complexity: constant (O(1))
*
* @returns The data of the node that was removed.
* @throws Index out of bounds if the list is empty.
*/
pop(): T {
if (this.isEmpty()) {
throw new Error('Index out of bounds');
}

const node: ListNode<T> = this.head!;
this.head = this.head!.next;
this.length--;

return node.data;
}

/**
* Inserts the given data as a new node after the current TAIL.
* Time complexity: constant (O(1))
*
* @param data The data of the node being inserted.
*/
append(data: T): void {
const node: ListNode<T> = new ListNode<T>(data);

if (this.isEmpty()) {
this.head = node;
} else {
this.tail!.next = node;
}

this.tail = node;
this.length++;
}

/**
* Removes the current TAIL of the list.
* Time complexity: linear (O(n))
*
* @returns The data of the former TAIL.
* @throws Index out of bounds if the list is empty.
*/
removeTail(): T {
if (!this.head) {
throw new Error('Index out of bounds');
}

const currentTail = this.tail;
if (this.head === this.tail) {
this.head = undefined;
this.tail = undefined;
this.length--;

return currentTail!.data;
}

let currentNode: ListNode<T> = this.head;
while (currentNode.next !== currentTail) {
currentNode = currentNode.next!;
}

this.tail = currentNode;
this.length--;

return currentTail!.data;
}

/**
* Inserts the data as a new node at the given index.
* Time complexity: O(n)
*
* @param index The index where the node is to be inserted.
* @param data The data to insert.
* @throws Index out of bounds, when given an invalid index.
*/
insertAt(index: number, data: T): void {
if (index < 0 || index > this.length) {
throw new Error('Index out of bounds');
}

if (index === 0) {
this.push(data);

return;
}

if (index === this.length) {
this.append(data);

return;
}

const newNode = new ListNode<T>(data);
let currentNode: ListNode<T> | undefined = this.head;
for (let i: number = 0; i < index - 1; i++) {
currentNode = currentNode?.next;
}

const nextNode = currentNode?.next;
currentNode!.next = newNode;
newNode.next = nextNode;

this.length++;
}

/**
* Removes the node at the given index.
* Time complexity: O(n)
*
* @param index The index of the node to be removed.
* @returns The data of the removed node.
* @throws Index out of bounds, when given an invalid index.
*/
removeAt(index: number): T {
if (index < 0 || index >= this.length) {
throw new Error('Index out of bounds');
}

if (index === 0) {
return this.pop();
}

if (index === this.length - 1) {
return this.removeTail();
}

let previousNode: ListNode<T> | undefined;
let currentNode: ListNode<T> | undefined = this.head;
for (let i: number = 0; i < index; i++) {
if (i === index - 1) {
previousNode = currentNode;
}

currentNode = currentNode?.next;
}

previousNode!.next = currentNode?.next;
this.length--;

return currentNode!.data;
}

/**
* Clears the list.
*/
clear(): void {
this.head = undefined;
this.tail = undefined;
this.length = 0;
}

/**
* Converts the list to an array.
*
* @returns The array representation of the list.
*/
toArray(): T[] {
const array: T[] = [];
let currentNode: ListNode<T> | undefined = this.head;

while (currentNode) {
array.push(currentNode.data);
currentNode = currentNode.next;
}

return array;
}

/**
* Gets the length of the list.
*
* @returns The length of the list.
*/
getLength(): number {
return this.length;
}
}
Loading