Initial Commit - Sonnet 4.5

This commit is contained in:
2025-11-22 18:18:23 -05:00
commit 74fd80f91c
21 changed files with 2621 additions and 0 deletions

279
src/core/HashMap.ts Normal file
View File

@@ -0,0 +1,279 @@
import type { IHashMap } from "../interfaces/IHashMap.ts";
import type { IHashFunction } from "../interfaces/IHashFunction.ts";
import { HashNode } from "../models/HashNode.ts";
import { DefaultHashFunction } from "../hash-functions/DefaultHashFunction.ts";
/**
* HashMap implementation using separate chaining for collision resolution.
* Follows SOLID principles:
* - SRP: Focused on hash map operations
* - OCP: Extensible through custom hash functions
* - LSP: Implements IHashMap interface correctly
* - ISP: Uses focused interfaces
* - DIP: Depends on IHashFunction abstraction
*
* @template K - The type of keys
* @template V - The type of values
*/
export class HashMap<K, V> implements IHashMap<K, V>, Iterable<[K, V]> {
private buckets: (HashNode<K, V> | null)[];
private _size: number = 0;
private readonly hashFunction: IHashFunction<K>;
private readonly loadFactorThreshold: number;
private readonly initialCapacity: number;
/**
* Creates a new HashMap instance.
* @param initialCapacity - Initial number of buckets (default: 16)
* @param loadFactorThreshold - Threshold for resizing (default: 0.75)
* @param hashFunction - Custom hash function (optional)
*/
constructor(
initialCapacity: number = 16,
loadFactorThreshold: number = 0.75,
hashFunction?: IHashFunction<K>
) {
if (initialCapacity <= 0) {
throw new Error("Initial capacity must be positive");
}
if (loadFactorThreshold <= 0 || loadFactorThreshold > 1) {
throw new Error("Load factor must be between 0 and 1");
}
this.initialCapacity = initialCapacity;
this.buckets = new Array(initialCapacity).fill(null);
this.loadFactorThreshold = loadFactorThreshold;
this.hashFunction = hashFunction ?? new DefaultHashFunction<K>();
}
/**
* Gets the current number of key-value pairs in the map.
*/
get size(): number {
return this._size;
}
/**
* Gets the current capacity (number of buckets).
*/
get capacity(): number {
return this.buckets.length;
}
/**
* Gets the current load factor.
*/
get loadFactor(): number {
return this._size / this.buckets.length;
}
/**
* Inserts or updates a key-value pair in the map.
* Time Complexity: Average O(1), Worst O(n)
*/
set(key: K, value: V): void {
this.ensureCapacity();
const index = this.hashFunction.hash(key, this.buckets.length);
let node = this.buckets[index] ?? null;
// Check if key already exists
while (node !== null) {
if (this.keysEqual(node.key, key)) {
node.value = value; // Update existing value
return;
}
node = node.next;
}
// Insert new node at the beginning of the chain
const newNode = new HashNode(key, value, this.buckets[index] ?? null);
this.buckets[index] = newNode;
this._size++;
}
/**
* Retrieves the value associated with the given key.
* Time Complexity: Average O(1), Worst O(n)
*/
get(key: K): V | undefined {
const index = this.hashFunction.hash(key, this.buckets.length);
let node = this.buckets[index] ?? null;
while (node !== null) {
if (this.keysEqual(node.key, key)) {
return node.value;
}
node = node.next;
}
return undefined;
}
/**
* Checks if a key exists in the map.
* Time Complexity: Average O(1), Worst O(n)
*/
has(key: K): boolean {
const index = this.hashFunction.hash(key, this.buckets.length);
let node = this.buckets[index] ?? null;
while (node !== null) {
if (this.keysEqual(node.key, key)) {
return true;
}
node = node.next;
}
return false;
}
/**
* Removes a key-value pair from the map.
* Time Complexity: Average O(1), Worst O(n)
*/
delete(key: K): boolean {
const index = this.hashFunction.hash(key, this.buckets.length);
let node = this.buckets[index] ?? null;
let prev: HashNode<K, V> | null = null;
while (node !== null) {
if (this.keysEqual(node.key, key)) {
if (prev === null) {
// Remove head node
this.buckets[index] = node.next;
} else {
// Remove middle or tail node
prev.next = node.next;
}
this._size--;
return true;
}
prev = node;
node = node.next;
}
return false;
}
/**
* Removes all key-value pairs from the map.
* Time Complexity: O(n) where n is the capacity
*/
clear(): void {
this.buckets = new Array(this.initialCapacity).fill(null);
this._size = 0;
}
/**
* Returns an iterator over the keys in the map.
*/
*keys(): IterableIterator<K> {
for (const bucket of this.buckets) {
let node = bucket ?? null;
while (node !== null) {
yield node.key;
node = node.next;
}
}
}
/**
* Returns an iterator over the values in the map.
*/
*values(): IterableIterator<V> {
for (const bucket of this.buckets) {
let node = bucket ?? null;
while (node !== null) {
yield node.value;
node = node.next;
}
}
}
/**
* Returns an iterator over the key-value pairs in the map.
*/
*entries(): IterableIterator<[K, V]> {
for (const bucket of this.buckets) {
let node = bucket ?? null;
while (node !== null) {
yield [node.key, node.value];
node = node.next;
}
}
}
/**
* Makes the HashMap iterable (for...of loops).
*/
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
/**
* Executes a callback for each key-value pair in the map.
*/
forEach(callback: (value: V, key: K, map: IHashMap<K, V>) => void): void {
for (const [key, value] of this.entries()) {
callback(value, key, this);
}
}
/**
* Returns a string representation of the map.
*/
toString(): string {
const entries: string[] = [];
for (const [key, value] of this.entries()) {
entries.push(`${String(key)} => ${String(value)}`);
}
return `HashMap(${this._size}) { ${entries.join(", ")} }`;
}
/**
* Checks if two keys are equal.
* Handles primitive types and object references.
*/
private keysEqual(key1: K, key2: K): boolean {
// Handle NaN case
if (typeof key1 === "number" && typeof key2 === "number") {
if (Number.isNaN(key1) && Number.isNaN(key2)) {
return true;
}
}
// Standard equality
return key1 === key2;
}
/**
* Ensures the map has sufficient capacity.
* Resizes if load factor exceeds threshold.
*/
private ensureCapacity(): void {
if (this.loadFactor >= this.loadFactorThreshold) {
this.resize(this.buckets.length * 2);
}
}
/**
* Resizes the hash table to the new capacity.
* Rehashes all existing entries.
*/
private resize(newCapacity: number): void {
const oldBuckets = this.buckets;
this.buckets = new Array(newCapacity).fill(null);
this._size = 0;
// Rehash all existing entries
for (const bucket of oldBuckets) {
let node = bucket ?? null;
while (node !== null) {
this.set(node.key, node.value);
node = node.next;
}
}
}
}