Skip to content
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
133 changes: 133 additions & 0 deletions src/06-linked-list/__test__/circular-linked-list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {describe, expect, test, beforeEach} from '@jest/globals';
import CircularLinkedList from '../circular-linked-list';

describe('CircularLinkedList', () => {
let circularList: CircularLinkedList<number>;

beforeEach(() => {
circularList = new CircularLinkedList<number>();
});

test('should create an empty list', () => {
expect(circularList.isEmpty()).toBe(true);
expect(circularList.getSize()).toBe(0);
expect(circularList.toString()).toBe('');
});

test('should append a single element', () => {
circularList.append(1);
expect(circularList.toString()).toBe('1');
expect(circularList.getSize()).toBe(1);
});

test('should append multiple elements', () => {
circularList.append(1);
circularList.append(2);
circularList.append(3);
expect(circularList.toString()).toBe('1 -> 2 -> 3');
});

test('should prepend an element', () => {
circularList.append(2);
circularList.append(3);
circularList.prepend(1);
expect(circularList.toString()).toBe('1 -> 2 -> 3');
expect(circularList.getSize()).toBe(3);
});

test('should insert at position 0 (delegates to prepend)', () => {
circularList.append(2);
circularList.append(3);
circularList.insert(0, 1);
expect(circularList.toString()).toBe('1 -> 2 -> 3');
});

test('should insert at a middle position', () => {
circularList.append(1);
circularList.append(3);
circularList.insert(1, 2);
expect(circularList.toString()).toBe('1 -> 2 -> 3');
});

test('should return false for insert at invalid position', () => {
circularList.append(1);
expect(circularList.insert(5, 99)).toBe(false);
expect(circularList.getSize()).toBe(1);
});

test('should removeAt head (position 0)', () => {
circularList.append(1);
circularList.append(2);
circularList.append(3);
circularList.removeAt(0);
expect(circularList.toString()).toBe('2 -> 3');
expect(circularList.getSize()).toBe(2);
});

test('should removeAt a middle position', () => {
circularList.append(1);
circularList.append(2);
circularList.append(3);
circularList.removeAt(1);
expect(circularList.toString()).toBe('1 -> 3');
});

test('should throw for removeAt invalid position', () => {
circularList.append(1);
expect(() => circularList.removeAt(5)).toThrow('Invalid position');
});

test('should remove an element by value', () => {
circularList.append(1);
circularList.append(2);
circularList.append(3);
circularList.remove(2);
expect(circularList.toString()).toBe('1 -> 3');
});

test('should return null when removing a non-existing element', () => {
circularList.append(1);
expect(circularList.remove(99)).toBeNull();
});

test('should find indexOf an existing element', () => {
circularList.append(10);
circularList.append(20);
circularList.append(30);
expect(circularList.indexOf(10)).toBe(0);
expect(circularList.indexOf(20)).toBe(1);
expect(circularList.indexOf(30)).toBe(2);
});

test('should return -1 for indexOf a non-existing element', () => {
circularList.append(1);
expect(circularList.indexOf(99)).toBe(-1);
});

test('should clear the list', () => {
circularList.append(1);
circularList.append(2);
circularList.clear();
expect(circularList.isEmpty()).toBe(true);
expect(circularList.toString()).toBe('');
});

test('should maintain circular structure after append', () => {
circularList.append(1);
circularList.append(2);
circularList.append(3);
const head = circularList.getHead();
expect(head?.next?.next?.next).toBe(head);
});

test('should reverse the list', () => {
circularList.append(1);
circularList.append(2);
circularList.append(3);
circularList.reverse();
const head = circularList.getHead();
expect(head?.element).toBe(3);
expect(head?.next?.element).toBe(2);
expect(head?.next?.next?.element).toBe(1);
});
});
54 changes: 54 additions & 0 deletions src/08-dictionary-hash/__test__/hash-table.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {describe, expect, test, beforeEach} from '@jest/globals';
import HashTable from '../hash-table';

describe('HashTable', () => {
let hashTable: HashTable<string>;

beforeEach(() => {
hashTable = new HashTable<string>();
});

test('should put and get a value', () => {
hashTable.put('name', 'Alice');
expect(hashTable.get('name')).toBe('Alice');
});

test('should return undefined for a non-existent key', () => {
expect(hashTable.get('missing')).toBeUndefined();
});

test('should overwrite an existing key with put', () => {
hashTable.put('name', 'Alice');
hashTable.put('name', 'Bob');
expect(hashTable.get('name')).toBe('Bob');
});

test('should remove an existing key and return true', () => {
hashTable.put('name', 'Alice');
expect(hashTable.remove('name')).toBe(true);
expect(hashTable.get('name')).toBeUndefined();
});

test('should return false when removing a non-existent key', () => {
expect(hashTable.remove('missing')).toBe(false);
});

test('should store multiple keys', () => {
hashTable.put('name', 'Alice');
hashTable.put('city', 'London');
expect(hashTable.get('name')).toBe('Alice');
expect(hashTable.get('city')).toBe('London');
});

test('hash function returns consistent results', () => {
expect(hashTable.hash('name')).toBe(hashTable.hash('name'));
expect(hashTable.hash('abc')).toBeGreaterThanOrEqual(0);
expect(hashTable.hash('abc')).toBeLessThan(37);
});

test('should produce correct toString output', () => {
hashTable.put('name', 'Alice');
const result = hashTable.toString();
expect(result).toContain('Alice');
});
});
13 changes: 5 additions & 8 deletions src/08-dictionary-hash/hash-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ class HashTable<V> {
private table: V[] = [];

#loseLoseHashCode(key: string) {
// if (typeof key !== 'string') {
// key = this.#elementToString(key);
// }
const calcASCIIValue = (acc: number, char: string) => acc + char.charCodeAt(0);
const hash = key.split('').reduce(calcASCIIValue, 0);
return hash % 37;
Expand All @@ -19,21 +16,21 @@ class HashTable<V> {

put(key: string, value: V) {
const index = this.hash(key);
this.table[index] = value;
this.table[index] = value;
return true;
}

get(key: string): V {
get(key: string): V | undefined {
const index = this.hash(key);
return this.table[index];
}

remove(key: string) {
remove(key: string): boolean {
if (key == null) {
return false;
}
const index = this.hash(key);
if (this.table[index]) {
if (this.table[index] != null) {
delete this.table[index];
return true;
}
Expand All @@ -44,7 +41,7 @@ class HashTable<V> {
if (typeof data === 'object' && data !== null) {
return JSON.stringify(data);
} else {
return String(data);
return String(data);
}
}

Expand Down
83 changes: 83 additions & 0 deletions src/12-trie/__test__/trie.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {describe, expect, test, beforeEach} from '@jest/globals';
import Trie from '../trie';

describe('Trie', () => {
let trie: Trie;

beforeEach(() => {
trie = new Trie();
});

test('should return false for search on empty trie', () => {
expect(trie.search('hello')).toBe(false);
});

test('should insert and search a word', () => {
trie.insert('hello');
expect(trie.search('hello')).toBe(true);
});

test('should return false for a word that does not exist', () => {
trie.insert('hello');
expect(trie.search('world')).toBe(false);
});

test('should return false when searching a prefix that is not a full word', () => {
trie.insert('hello');
expect(trie.search('hell')).toBe(false);
});

test('should return true for startsWith with an existing prefix', () => {
trie.insert('hello');
expect(trie.startsWith('hel')).toBe(true);
});

test('should return false for startsWith with a non-existing prefix', () => {
trie.insert('hello');
expect(trie.startsWith('xyz')).toBe(false);
});

test('should return true for startsWith when the full word is the prefix', () => {
trie.insert('hello');
expect(trie.startsWith('hello')).toBe(true);
});

test('should insert multiple words with shared prefix', () => {
trie.insert('car');
trie.insert('card');
trie.insert('care');
expect(trie.search('car')).toBe(true);
expect(trie.search('card')).toBe(true);
expect(trie.search('care')).toBe(true);
expect(trie.startsWith('car')).toBe(true);
expect(trie.search('ca')).toBe(false);
});

test('should remove an existing word so search returns false', () => {
trie.insert('hello');
trie.remove('hello');
expect(trie.search('hello')).toBe(false);
});

test('should remove a word that is a prefix of another without affecting the longer word', () => {
trie.insert('car');
trie.insert('card');
trie.remove('car');
expect(trie.search('car')).toBe(false);
expect(trie.search('card')).toBe(true);
});

test('should return false when removing a non-existent word', () => {
trie.insert('hello');
expect(trie.remove('world')).toBe(false);
});

test('should keep startsWith working after removing a word that shares a prefix', () => {
trie.insert('car');
trie.insert('card');
trie.remove('card');
expect(trie.search('card')).toBe(false);
expect(trie.search('car')).toBe(true);
expect(trie.startsWith('car')).toBe(true);
});
});
2 changes: 1 addition & 1 deletion src/12-trie/trie.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Trie {
const shouldDeleteCurrentNode = this.#removeWord(node.children.get(char), word, index + 1);
if (shouldDeleteCurrentNode) {
node.children.delete(char);
return node.children.size === 0;
return node.children.size === 0 && !node.isEndOfWord;
}

return false;
Expand Down
2 changes: 1 addition & 1 deletion src/12-trie/trie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class Trie {
const shouldDeleteCurrentNode = this.#removeWord(node.children.get(char)!, word, index + 1);
if (shouldDeleteCurrentNode) {
node.children.delete(char);
return node.children.size === 0;
return node.children.size === 0 && !node.isEndOfWord;
}

return false;
Expand Down
Loading