diff --git a/src/06-linked-list/__test__/circular-linked-list.test.ts b/src/06-linked-list/__test__/circular-linked-list.test.ts new file mode 100644 index 00000000..2d61d380 --- /dev/null +++ b/src/06-linked-list/__test__/circular-linked-list.test.ts @@ -0,0 +1,133 @@ +import {describe, expect, test, beforeEach} from '@jest/globals'; +import CircularLinkedList from '../circular-linked-list'; + +describe('CircularLinkedList', () => { + let circularList: CircularLinkedList; + + beforeEach(() => { + circularList = new CircularLinkedList(); + }); + + 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); + }); +}); diff --git a/src/08-dictionary-hash/__test__/hash-table.test.ts b/src/08-dictionary-hash/__test__/hash-table.test.ts new file mode 100644 index 00000000..59e26ebe --- /dev/null +++ b/src/08-dictionary-hash/__test__/hash-table.test.ts @@ -0,0 +1,54 @@ +import {describe, expect, test, beforeEach} from '@jest/globals'; +import HashTable from '../hash-table'; + +describe('HashTable', () => { + let hashTable: HashTable; + + beforeEach(() => { + hashTable = new HashTable(); + }); + + 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'); + }); +}); diff --git a/src/08-dictionary-hash/hash-table.ts b/src/08-dictionary-hash/hash-table.ts index d7e1c1da..df32bbfd 100644 --- a/src/08-dictionary-hash/hash-table.ts +++ b/src/08-dictionary-hash/hash-table.ts @@ -5,9 +5,6 @@ class HashTable { 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; @@ -19,21 +16,21 @@ class HashTable { 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; } @@ -44,7 +41,7 @@ class HashTable { if (typeof data === 'object' && data !== null) { return JSON.stringify(data); } else { - return String(data); + return String(data); } } diff --git a/src/12-trie/__test__/trie.test.ts b/src/12-trie/__test__/trie.test.ts new file mode 100644 index 00000000..13981929 --- /dev/null +++ b/src/12-trie/__test__/trie.test.ts @@ -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); + }); +}); diff --git a/src/12-trie/trie.js b/src/12-trie/trie.js index d61298a2..44a6eae0 100644 --- a/src/12-trie/trie.js +++ b/src/12-trie/trie.js @@ -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; diff --git a/src/12-trie/trie.ts b/src/12-trie/trie.ts index 080921b8..7cc81789 100644 --- a/src/12-trie/trie.ts +++ b/src/12-trie/trie.ts @@ -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;