Files
hash-map/tests/HashFunctions.test.ts

324 lines
9.6 KiB
TypeScript

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: { name: string; self?: unknown } = { 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);
});
});
});