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