From 691673ae4d69f30d67955884eba4b29af4883ca9 Mon Sep 17 00:00:00 2001 From: Sven Palberg Date: Sat, 7 Dec 2024 12:29:29 +0100 Subject: [PATCH] more datastructures --- lib/datastructures/grid/grid.ts | 11 ++- lib/datastructures/grid/grid_test.ts | 68 ++++++------- lib/datastructures/vector/vector.ts | 117 +++++++++++++++++++---- lib/datastructures/vector/vector_test.ts | 67 ++++++++----- 4 files changed, 184 insertions(+), 79 deletions(-) diff --git a/lib/datastructures/grid/grid.ts b/lib/datastructures/grid/grid.ts index d962dc7..b88f188 100644 --- a/lib/datastructures/grid/grid.ts +++ b/lib/datastructures/grid/grid.ts @@ -1,13 +1,18 @@ import { Vector } from "../vector/mod.ts"; +import { ArrayVector } from "../vector/vector.ts"; export class Grid { #data: Array>; - constructor(data: Array>) { + private constructor(data: Array>) { this.#data = data; this.#validatekDimensions(); } + static from(data: Array>): Grid { + return new Grid(data); + } + static fromStrings(strings: Array): Grid { return new Grid(strings.map((line) => line.split(""))); } @@ -26,7 +31,7 @@ export class Grid { *rows(): Generator> { for (const row of this.#data) { - yield new Vector(row); + yield ArrayVector.from(row); } } @@ -51,7 +56,7 @@ export class GridColumnVector extends Vector { #x: number; constructor(grid: Grid, x: number) { - super([]); + super(); this.#grid = grid; this.#x = x; } diff --git a/lib/datastructures/grid/grid_test.ts b/lib/datastructures/grid/grid_test.ts index a6623ad..ed4b34b 100644 --- a/lib/datastructures/grid/grid_test.ts +++ b/lib/datastructures/grid/grid_test.ts @@ -1,64 +1,51 @@ import { expect } from "@std/expect"; import { describe, it } from "@std/testing/bdd"; -import { Grid } from "./grid.ts"; +import { vectorImplTestSuite } from "../vector/vector_test.ts"; +import { Grid, GridColumnVector } from "./grid.ts"; -const defaultGrid = new Grid([ - [1, 2, 3], - [4, 5, 6], -]); +describe("Grid", () => { + const smallTestGrid = Grid.from([ + [1, 2, 3], + [4, 5, 6], + ]); -describe("Grid::new", () => { - it("should handle well formed 2d arrays", () => { - expect(defaultGrid.height).toBe(2); - expect(defaultGrid.width).toBe(3); - }); - - it("should throw on malformed 2d arrays", () => { + it("factory from", () => { + expect(smallTestGrid.height).toBe(2); + expect(smallTestGrid.width).toBe(3); expect(() => { - new Grid([ + Grid.from([ [1, 2, 3], [4, 5], ]); }).toThrow("Rows have different lengths"); - }); - - it("should throw on empty 2d arrays", () => { expect(() => { - new Grid([]); + Grid.from([]); }).toThrow("Grid is empty"); }); -}); -describe("Grid.fromStrings", () => { - it("should handle well formed strings", () => { + it("factory fromStrings", () => { const grid = Grid.fromStrings(["123", "456"]); expect(grid.height).toBe(2); expect(grid.width).toBe(3); }); -}); -describe("Grid::at", () => { - it("should return the value at the given coordinates", () => { - expect(defaultGrid.at(1, 1)).toBe(5); - expect(defaultGrid.at(2, 0)).toBe(3); - expect(defaultGrid.at(2, 2)).toBe(null); + it("method at", () => { + expect(smallTestGrid.at(1, 1)).toBe(5); + expect(smallTestGrid.at(2, 0)).toBe(3); + expect(smallTestGrid.at(2, 2)).toBe(null); }); -}); -describe("Grid::rows", () => { - it("should yield rows", () => { - const rows = [...defaultGrid.rows()]; + it("method rows", () => { + const rows = [...smallTestGrid.rows()]; expect(rows.length).toBe(2); expect(rows[0].length).toBe(3); expect(rows[0].at(0)).toBe(1); expect(rows[1].length).toBe(3); expect(rows[1].at(0)).toBe(4); }); -}); -describe("Grid::columns", () => { - it("should yield columns", () => { - const columns = [...defaultGrid.columns()]; + it("method columns", () => { + const columns = [...smallTestGrid.columns()]; expect(columns.length).toBe(3); expect(columns[0].length).toBe(2); expect(columns[0].at(0)).toBe(1); @@ -67,3 +54,16 @@ describe("Grid::columns", () => { expect(columns[1].at(0)).toBe(2); }); }); + +describe("GridColumnVector", () => { + const grid = Grid.from([ + [0, 1, 0], + [0, 2, 0], + [0, 3, 0], + [0, 4, 0], + [0, 5, 0], + [0, 6, 0], + ]); + const gridColumn = new GridColumnVector(grid, 1); + vectorImplTestSuite(gridColumn); +}); diff --git a/lib/datastructures/vector/vector.ts b/lib/datastructures/vector/vector.ts index d1d16ed..4155ed5 100644 --- a/lib/datastructures/vector/vector.ts +++ b/lib/datastructures/vector/vector.ts @@ -1,28 +1,19 @@ -export class Vector { - #data: Array; - #start: number; - #end: number; - - constructor( - data: Array, - start: number = 0, - end: number = data.length, - ) { - this.#data = data; - this.#start = start; - this.#end = end; +export abstract class Vector { + static from(data: Array): Vector { + return new ArrayVector(data); } static fromString(data: string): Vector { - return new Vector(data.split("")); + return new ArrayVector(data.split("")); } - get length(): number { - return this.#end - this.#start; - } + abstract get length(): number; - at(index: number): T | null { - return this.#data[this.#start + index] ?? null; + abstract at(index: number): T | null; + + toString(joinStr = ","): string { + const values = [...this].join(joinStr); + return `[${values}]`; } equals(other: Vector): boolean { @@ -36,4 +27,92 @@ export class Vector { } return true; } + + indexOf(value: T | Vector): number { + if (value instanceof Vector) { + return this.#indexOfVector(value); + } + return this.#indexOfValue(value); + } + + contains(value: T | Vector): boolean { + return this.indexOf(value) !== -1; + } + + *[Symbol.iterator](): Generator { + for (let i = 0; i < this.length; i++) { + yield this.at(i)!; + } + } + + [Symbol.toStringTag]: string = "Vector"; + + #indexOfValue(value: T): number { + for (let i = 0; i < this.length; i++) { + if (this.at(i) === value) { + return i; + } + } + return -1; + } + + #indexOfVector(vector: Vector): number { + if (this.length < vector.length) { + return -1; + } + for (let i = 0; i < this.length - vector.length; i++) { + let found = true; + for (let j = 0; j < vector.length; j++) { + if (this.at(i + j) !== vector.at(j)) { + found = false; + break; + } + } + if (found) { + return i; + } + } + return -1; + } +} + +export class ArrayVector extends Vector { + #data: Array; + + constructor(data: Array) { + super(); + this.#data = data; + } + + override get length(): number { + return this.#data.length; + } + + override at(index: number): T | null { + return this.#data[index] ?? null; + } +} + +export class SubVector extends Vector { + #vector: Vector; + #start: number; + #end: number; + + constructor(vector: Vector, start: number, end: number) { + super(); + this.#vector = vector; + this.#start = start; + this.#end = end; + } + + override get length(): number { + return this.#end - this.#start; + } + + override at(index: number): T | null { + if (index < 0 || index >= this.length) { + return null; + } + return this.#vector.at(this.#start + index); + } } diff --git a/lib/datastructures/vector/vector_test.ts b/lib/datastructures/vector/vector_test.ts index d8379e3..048f99f 100644 --- a/lib/datastructures/vector/vector_test.ts +++ b/lib/datastructures/vector/vector_test.ts @@ -2,35 +2,56 @@ import { expect } from "@std/expect"; import { describe, it } from "@std/testing/bdd"; import { Vector } from "./vector.ts"; -const defaultVector = new Vector([1, 2, 3, 4, 5, 6]); +describe("Vector", () => { + it("factory from", () => { + expect(Vector.from([1, 2, 3, 4, 5, 6]).length).toBe(6); + expect(Vector.from([]).length).toBe(0); + expect(Vector.from([{ x: 1, y: 2 }, { x: 3, y: 4 }]).length).toBe(2); + }); -describe("Vector::new", () => { - it("should handle arrays", () => { - expect(defaultVector.length).toBe(6); - expect(new Vector([]).length).toBe(0); - expect(new Vector([{ x: 1, y: 2 }, { x: 3, y: 4 }]).length).toBe(2); + it("factory fromStrings", () => { + expect(Vector.fromString("123456").length).toBe(6); }); }); -describe("Vector.fromStrings", () => { - it("should work", () => { - const vector = Vector.fromString("123456"); - expect(vector.length).toBe(6); - }); +describe("ArrayVector", () => { + vectorImplTestSuite(Vector.from([1, 2, 3, 4, 5, 6])); }); -describe("Vector::at", () => { - it("should return the value at the given index", () => { - expect(defaultVector.at(0)).toBe(1); - expect(defaultVector.at(5)).toBe(6); - expect(defaultVector.at(6)).toBe(null); - expect(defaultVector.at(-1)).toBe(null); - }); +describe("SubVector", () => { }); -describe("Vector::equals", () => { - it("should return true if the vectors are equal", () => { - expect(defaultVector.equals(new Vector([1, 2, 3, 4, 5, 6]))).toBe(true); - expect(defaultVector.equals(new Vector([1, 2, 3, 4, 5]))).toBe(false); +export function vectorImplTestSuite(vector: Vector) { + it("method at", () => { + expect(vector.at(0)).toBe(1); + expect(vector.at(5)).toBe(6); + expect(vector.at(6)).toBe(null); + expect(vector.at(-1)).toBe(null); }); -}); + + it("method toString", () => { + expect(vector.toString()).toBe("[1,2,3,4,5,6]"); + expect(Vector.from([]).toString()).toBe("[]"); + }); + + it("method equals", () => { + expect(vector.equals(Vector.from([1, 2, 3, 4, 5, 6]))).toBe(true); + expect(vector.equals(Vector.from([1, 2, 3, 4, 5]))).toBe(false); + }); + + it("method indexOf", () => { + expect(vector.indexOf(1)).toBe(0); + expect(vector.indexOf(3)).toBe(2); + expect(vector.indexOf(6)).toBe(5); + expect(vector.indexOf(7)).toBe(-1); + expect(vector.indexOf(Vector.from([3, 4, 5]))).toBe(2); + expect(vector.indexOf(Vector.from([3, 4, 7]))).toBe(-1); + expect(vector.indexOf(Vector.from([]))).toBe(0); + expect(vector.indexOf(Vector.from([1, 2, 3, 4, 5, 6, 7]))).toBe(-1); + }); + + it("method [Symbol.iterator]", () => { + const values = [...vector]; + expect(values).toEqual([1, 2, 3, 4, 5, 6]); + }); +}