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

324
tests/HashFunctions.test.ts Normal file
View File

@@ -0,0 +1,324 @@
import { describe, it, expect } from "bun:test";
import { DefaultHashFunction } from "../src/hash-functions/DefaultHashFunction.ts";
import { NumericHashFunction } from "../src/hash-functions/NumericHashFunction.ts";
describe("DefaultHashFunction", () => {
const hashFn = new DefaultHashFunction<unknown>();
const capacity = 16;
describe("basic types", () => {
it("should hash string keys", () => {
const hash1 = hashFn.hash("hello", capacity);
const hash2 = hashFn.hash("world", capacity);
expect(hash1).toBeGreaterThanOrEqual(0);
expect(hash1).toBeLessThan(capacity);
expect(hash2).toBeGreaterThanOrEqual(0);
expect(hash2).toBeLessThan(capacity);
});
it("should hash number keys", () => {
const hash1 = hashFn.hash(42, capacity);
const hash2 = hashFn.hash(3.14, capacity);
const hash3 = hashFn.hash(0, capacity);
const hash4 = hashFn.hash(-10, capacity);
expect(hash1).toBeGreaterThanOrEqual(0);
expect(hash1).toBeLessThan(capacity);
expect(hash2).toBeGreaterThanOrEqual(0);
expect(hash3).toBeGreaterThanOrEqual(0);
expect(hash4).toBeGreaterThanOrEqual(0);
});
it("should hash boolean keys", () => {
const hashTrue = hashFn.hash(true, capacity);
const hashFalse = hashFn.hash(false, capacity);
expect(hashTrue).toBeGreaterThanOrEqual(0);
expect(hashTrue).toBeLessThan(capacity);
expect(hashFalse).toBeGreaterThanOrEqual(0);
expect(hashFalse).toBeLessThan(capacity);
});
it("should hash null", () => {
const hash = hashFn.hash(null, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash undefined", () => {
const hash = hashFn.hash(undefined, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
});
describe("object types", () => {
it("should hash simple objects", () => {
const obj = { name: "Alice", age: 30 };
const hash = hashFn.hash(obj, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash arrays", () => {
const arr = [1, 2, 3, 4, 5];
const hash = hashFn.hash(arr, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash nested objects", () => {
const nested = {
user: {
name: "Bob",
address: {
city: "NYC",
zip: "10001"
}
}
};
const hash = hashFn.hash(nested, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should handle circular references gracefully", () => {
const circular: any = { name: "test" };
circular.self = circular; // Create circular reference
// Should not throw, should fall back to Object.prototype.toString
const hash = hashFn.hash(circular, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash Date objects", () => {
const date = new Date("2024-01-01");
const hash = hashFn.hash(date, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash RegExp objects", () => {
const regex = /test/g;
const hash = hashFn.hash(regex, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash Error objects", () => {
const error = new Error("Test error");
const hash = hashFn.hash(error, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
});
describe("special values", () => {
it("should hash empty string", () => {
const hash = hashFn.hash("", capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash empty object", () => {
const hash = hashFn.hash({}, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash empty array", () => {
const hash = hashFn.hash([], capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash symbols", () => {
const sym = Symbol("test");
const hash = hashFn.hash(sym, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash bigint", () => {
const bigInt = BigInt(12345678901234567890n);
const hash = hashFn.hash(bigInt, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
});
describe("consistency", () => {
it("should return same hash for same key", () => {
const key = "test-key";
const hash1 = hashFn.hash(key, capacity);
const hash2 = hashFn.hash(key, capacity);
expect(hash1).toBe(hash2);
});
it("should return different hashes for different keys (usually)", () => {
const hash1 = hashFn.hash("key1", capacity);
const hash2 = hashFn.hash("key2", capacity);
// Note: They COULD collide, but unlikely
expect(hash1).toBeGreaterThanOrEqual(0);
expect(hash2).toBeGreaterThanOrEqual(0);
});
it("should handle different capacities", () => {
const key = "test";
const hash8 = hashFn.hash(key, 8);
const hash16 = hashFn.hash(key, 16);
const hash32 = hashFn.hash(key, 32);
expect(hash8).toBeLessThan(8);
expect(hash16).toBeLessThan(16);
expect(hash32).toBeLessThan(32);
});
});
});
describe("NumericHashFunction", () => {
const hashFn = new NumericHashFunction();
const capacity = 16;
describe("normal numbers", () => {
it("should hash positive integers", () => {
const hash1 = hashFn.hash(42, capacity);
const hash2 = hashFn.hash(100, capacity);
expect(hash1).toBeGreaterThanOrEqual(0);
expect(hash1).toBeLessThan(capacity);
expect(hash2).toBeGreaterThanOrEqual(0);
expect(hash2).toBeLessThan(capacity);
});
it("should hash negative integers", () => {
const hash = hashFn.hash(-42, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash zero", () => {
const hash = hashFn.hash(0, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash floating point numbers", () => {
const hash1 = hashFn.hash(3.14159, capacity);
const hash2 = hashFn.hash(2.71828, capacity);
expect(hash1).toBeGreaterThanOrEqual(0);
expect(hash1).toBeLessThan(capacity);
expect(hash2).toBeGreaterThanOrEqual(0);
expect(hash2).toBeLessThan(capacity);
});
it("should hash very large numbers", () => {
const hash = hashFn.hash(Number.MAX_SAFE_INTEGER, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
it("should hash very small numbers", () => {
const hash = hashFn.hash(Number.MIN_VALUE, capacity);
expect(hash).toBeGreaterThanOrEqual(0);
expect(hash).toBeLessThan(capacity);
});
});
describe("special numeric values", () => {
it("should handle Infinity", () => {
const hash = hashFn.hash(Infinity, capacity);
// Should return 0 for non-finite numbers
expect(hash).toBe(0);
});
it("should handle negative Infinity", () => {
const hash = hashFn.hash(-Infinity, capacity);
// Should return 0 for non-finite numbers
expect(hash).toBe(0);
});
it("should handle NaN", () => {
const hash = hashFn.hash(NaN, capacity);
// Should return 0 for non-finite numbers
expect(hash).toBe(0);
});
});
describe("consistency", () => {
it("should return same hash for same number", () => {
const num = 42;
const hash1 = hashFn.hash(num, capacity);
const hash2 = hashFn.hash(num, capacity);
expect(hash1).toBe(hash2);
});
it("should handle different capacities", () => {
const num = 42;
const hash8 = hashFn.hash(num, 8);
const hash16 = hashFn.hash(num, 16);
const hash32 = hashFn.hash(num, 32);
expect(hash8).toBeLessThan(8);
expect(hash16).toBeLessThan(16);
expect(hash32).toBeLessThan(32);
});
it("should distribute numbers evenly", () => {
const hashes = new Set<number>();
// Hash 100 sequential numbers
for (let i = 0; i < 100; i++) {
hashes.add(hashFn.hash(i, capacity));
}
// Should have good distribution (not all in one bucket)
expect(hashes.size).toBeGreaterThan(1);
});
});
describe("negative numbers", () => {
it("should hash negative numbers correctly", () => {
const hash1 = hashFn.hash(-1, capacity);
const hash2 = hashFn.hash(-100, capacity);
const hash3 = hashFn.hash(-3.14, capacity);
expect(hash1).toBeGreaterThanOrEqual(0);
expect(hash1).toBeLessThan(capacity);
expect(hash2).toBeGreaterThanOrEqual(0);
expect(hash2).toBeLessThan(capacity);
expect(hash3).toBeGreaterThanOrEqual(0);
expect(hash3).toBeLessThan(capacity);
});
it("should handle same absolute value differently for positive and negative", () => {
const hashPos = hashFn.hash(42, capacity);
const hashNeg = hashFn.hash(-42, capacity);
// They should hash to the same bucket (due to Math.abs in implementation)
expect(hashPos).toBe(hashNeg);
});
});
});

362
tests/HashMap.test.ts Normal file
View File

@@ -0,0 +1,362 @@
import { describe, it, expect, beforeEach } from "bun:test";
import { HashMap, NumericHashFunction } from "../src/index.ts";
import type { IHashFunction } from "../src/index.ts";
describe("HashMap", () => {
let map: HashMap<string, number>;
beforeEach(() => {
map = new HashMap<string, number>();
});
describe("constructor", () => {
it("should create an empty map with default capacity", () => {
expect(map.size).toBe(0);
expect(map.capacity).toBe(16);
});
it("should create a map with custom initial capacity", () => {
const customMap = new HashMap<string, number>(32);
expect(customMap.capacity).toBe(32);
});
it("should throw error for invalid capacity", () => {
expect(() => new HashMap<string, number>(0)).toThrow();
expect(() => new HashMap<string, number>(-1)).toThrow();
});
it("should throw error for invalid load factor", () => {
expect(() => new HashMap<string, number>(16, 0)).toThrow();
expect(() => new HashMap<string, number>(16, 1.5)).toThrow();
});
});
describe("set and get", () => {
it("should set and get a value", () => {
map.set("key1", 100);
expect(map.get("key1")).toBe(100);
});
it("should update existing value", () => {
map.set("key1", 100);
map.set("key1", 200);
expect(map.get("key1")).toBe(200);
expect(map.size).toBe(1);
});
it("should return undefined for non-existent key", () => {
expect(map.get("nonexistent")).toBeUndefined();
});
it("should handle multiple key-value pairs", () => {
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
expect(map.get("a")).toBe(1);
expect(map.get("b")).toBe(2);
expect(map.get("c")).toBe(3);
expect(map.size).toBe(3);
});
});
describe("has", () => {
it("should return true for existing key", () => {
map.set("key1", 100);
expect(map.has("key1")).toBe(true);
});
it("should return false for non-existent key", () => {
expect(map.has("nonexistent")).toBe(false);
});
});
describe("delete", () => {
it("should delete existing key", () => {
map.set("key1", 100);
expect(map.delete("key1")).toBe(true);
expect(map.has("key1")).toBe(false);
expect(map.size).toBe(0);
});
it("should return false for non-existent key", () => {
expect(map.delete("nonexistent")).toBe(false);
});
it("should handle deletion from chain", () => {
// Add multiple items that might collide
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
map.delete("b");
expect(map.has("b")).toBe(false);
expect(map.has("a")).toBe(true);
expect(map.has("c")).toBe(true);
expect(map.size).toBe(2);
});
});
describe("clear", () => {
it("should remove all entries", () => {
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
map.clear();
expect(map.size).toBe(0);
expect(map.has("a")).toBe(false);
expect(map.has("b")).toBe(false);
expect(map.has("c")).toBe(false);
});
});
describe("size", () => {
it("should track size correctly", () => {
expect(map.size).toBe(0);
map.set("a", 1);
expect(map.size).toBe(1);
map.set("b", 2);
expect(map.size).toBe(2);
map.delete("a");
expect(map.size).toBe(1);
map.clear();
expect(map.size).toBe(0);
});
});
describe("keys", () => {
it("should iterate over all keys", () => {
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
const keys = Array.from(map.keys());
expect(keys).toHaveLength(3);
expect(keys).toContain("a");
expect(keys).toContain("b");
expect(keys).toContain("c");
});
it("should return empty iterator for empty map", () => {
const keys = Array.from(map.keys());
expect(keys).toHaveLength(0);
});
});
describe("values", () => {
it("should iterate over all values", () => {
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
const values = Array.from(map.values());
expect(values).toHaveLength(3);
expect(values).toContain(1);
expect(values).toContain(2);
expect(values).toContain(3);
});
});
describe("entries", () => {
it("should iterate over all entries", () => {
map.set("a", 1);
map.set("b", 2);
const entries = Array.from(map.entries());
expect(entries).toHaveLength(2);
expect(entries).toContainEqual(["a", 1]);
expect(entries).toContainEqual(["b", 2]);
});
});
describe("forEach", () => {
it("should iterate with forEach", () => {
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
const result: [string, number][] = [];
map.forEach((value, key) => {
result.push([key, value]);
});
expect(result).toHaveLength(3);
expect(result).toContainEqual(["a", 1]);
expect(result).toContainEqual(["b", 2]);
expect(result).toContainEqual(["c", 3]);
});
});
describe("iterable", () => {
it("should work with for...of", () => {
map.set("a", 1);
map.set("b", 2);
const entries: [string, number][] = [];
for (const entry of map) {
entries.push(entry);
}
expect(entries).toHaveLength(2);
expect(entries).toContainEqual(["a", 1]);
expect(entries).toContainEqual(["b", 2]);
});
});
describe("resizing", () => {
it("should resize when load factor exceeds threshold", () => {
const smallMap = new HashMap<string, number>(4, 0.75);
expect(smallMap.capacity).toBe(4);
// Add enough items to trigger resize
for (let i = 0; i < 10; i++) {
smallMap.set(`key${i}`, i);
}
expect(smallMap.size).toBe(10);
expect(smallMap.capacity).toBeGreaterThan(4);
// Verify all items are still accessible
for (let i = 0; i < 10; i++) {
expect(smallMap.get(`key${i}`)).toBe(i);
}
});
it("should maintain all entries after resize", () => {
const smallMap = new HashMap<string, number>(2, 0.5);
const entries = [
["a", 1],
["b", 2],
["c", 3],
["d", 4],
["e", 5],
] as const;
for (const [key, value] of entries) {
smallMap.set(key, value);
}
for (const [key, value] of entries) {
expect(smallMap.get(key)).toBe(value);
}
});
});
describe("custom hash function", () => {
it("should work with NumericHashFunction", () => {
const numMap = new HashMap<number, string>(
16,
0.75,
new NumericHashFunction()
);
numMap.set(123, "value1");
numMap.set(456, "value2");
expect(numMap.get(123)).toBe("value1");
expect(numMap.get(456)).toBe("value2");
});
it("should work with custom hash function", () => {
class SimpleHashFunction implements IHashFunction<string> {
hash(key: string, capacity: number): number {
return key.length % capacity;
}
}
const customMap = new HashMap<string, string>(
8,
0.75,
new SimpleHashFunction()
);
customMap.set("hi", "short");
customMap.set("hello", "medium");
expect(customMap.get("hi")).toBe("short");
expect(customMap.get("hello")).toBe("medium");
});
});
describe("edge cases", () => {
it("should handle null values", () => {
const nullMap = new HashMap<string, null>();
nullMap.set("key", null);
expect(nullMap.get("key")).toBeNull();
expect(nullMap.has("key")).toBe(true);
});
it("should handle undefined values", () => {
const undefinedMap = new HashMap<string, undefined>();
undefinedMap.set("key", undefined);
expect(undefinedMap.has("key")).toBe(true);
});
it("should handle empty string keys", () => {
map.set("", 100);
expect(map.get("")).toBe(100);
});
it("should handle numeric keys", () => {
const numMap = new HashMap<number, string>();
numMap.set(0, "zero");
numMap.set(1, "one");
numMap.set(-1, "negative one");
expect(numMap.get(0)).toBe("zero");
expect(numMap.get(1)).toBe("one");
expect(numMap.get(-1)).toBe("negative one");
});
it("should handle large number of entries", () => {
const largeMap = new HashMap<number, number>();
const count = 1000;
for (let i = 0; i < count; i++) {
largeMap.set(i, i * 2);
}
expect(largeMap.size).toBe(count);
for (let i = 0; i < count; i++) {
expect(largeMap.get(i)).toBe(i * 2);
}
});
});
describe("collision handling", () => {
it("should handle hash collisions correctly", () => {
// Create a map with small capacity to increase collision probability
const collisionMap = new HashMap<string, number>(2, 0.99);
collisionMap.set("a", 1);
collisionMap.set("b", 2);
collisionMap.set("c", 3);
collisionMap.set("d", 4);
expect(collisionMap.size).toBe(4);
expect(collisionMap.get("a")).toBe(1);
expect(collisionMap.get("b")).toBe(2);
expect(collisionMap.get("c")).toBe(3);
expect(collisionMap.get("d")).toBe(4);
});
});
describe("toString", () => {
it("should provide readable string representation", () => {
map.set("a", 1);
map.set("b", 2);
const str = map.toString();
expect(str).toContain("HashMap");
expect(str).toContain("2"); // size
});
});
});