Initial Commit - Sonnet 4.5
This commit is contained in:
279
src/core/HashMap.ts
Normal file
279
src/core/HashMap.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user