From aaebd736dd9c83e0c1e37b55e9e8228e9ece6e90 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Mon, 28 Feb 2022 19:57:05 +0100 Subject: [PATCH] Create separate classes, cache math answers --- src/main/js/Display.ts | 73 +++++++++++++++++++++++++++++++ src/main/js/Main.ts | 83 ++---------------------------------- src/main/js/PrimeMath.ts | 92 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 79 deletions(-) create mode 100644 src/main/js/Display.ts create mode 100644 src/main/js/PrimeMath.ts diff --git a/src/main/js/Display.ts b/src/main/js/Display.ts new file mode 100644 index 0000000..d9d1290 --- /dev/null +++ b/src/main/js/Display.ts @@ -0,0 +1,73 @@ +import {CachedPrimeMath, PrimeMath} from "./PrimeMath"; + + +const width: number = 100; +const height: number = 100; + + +export class Display { + private readonly scale: number = 15; + + private readonly canvas: HTMLCanvasElement; + private readonly primeMath: PrimeMath; + + + constructor(canvas: HTMLCanvasElement) { + this.canvas = canvas; + this.canvas.width = this.scale * width; + this.canvas.height = this.scale * height; + + this.primeMath = CachedPrimeMath.getInstance(); + } + + + public startDrawLoop(): void { + const cb = () => { + this.draw(); + window.requestAnimationFrame(cb); + }; + window.requestAnimationFrame(cb); + } + + + private draw(): void { + const ctx = this.canvas.getContext("2d")!; + + this.clearCanvas(ctx); + this.drawGrid(ctx); + this.drawPrimes(ctx); + } + + private clearCanvas(ctx: CanvasRenderingContext2D): void { + ctx.save(); + ctx.fillStyle = "#ffffff"; + ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + ctx.restore(); + } + + private drawGrid(ctx: CanvasRenderingContext2D): void { + ctx.save(); + ctx.beginPath(); + ctx.strokeStyle = "#7b7b7b"; + for (let x = 0; x <= width; x++) { + ctx.moveTo(x * this.scale, 0); + ctx.lineTo(x * this.scale, height * this.scale); + } + for (let y = 0; y <= height; y++) { + ctx.moveTo(0, y * this.scale); + ctx.lineTo(width * this.scale, y * this.scale); + } + ctx.stroke(); + ctx.restore(); + } + + private drawPrimes(ctx: CanvasRenderingContext2D): void { + ctx.save(); + ctx.fillStyle = "#ff0000"; + for (let i = 1; i <= width * height; i++) { + if (this.primeMath.isPrime(i)) + ctx.fillRect(((i - 1) % width) * this.scale, Math.floor((i - 1) / width) * this.scale, this.scale, this.scale); + } + ctx.restore(); + } +} diff --git a/src/main/js/Main.ts b/src/main/js/Main.ts index 70e8ed0..3c4fde9 100644 --- a/src/main/js/Main.ts +++ b/src/main/js/Main.ts @@ -1,6 +1,8 @@ // @ts-ignore const {$, doAfterLoad, footer, header, nav} = window.fwdekker; +import {Display} from "./Display"; + // Set up template doAfterLoad(() => { @@ -17,87 +19,10 @@ doAfterLoad(() => { }); -const scale: number = 15; -const width: number = 100; -const height: number = 100; - - -function isPrime(n: number): boolean { - if (!Number.isInteger(n)) return false; - - if (n < 2) return false; - if (n === 2) return true; - if (n % 2 === 0) return false; - - const limit = Math.floor(Math.sqrt(n)); - for (let i = 3; i <= limit; i += 2) - if (n % i === 0) - return false; - - return true; -} - -function decompose(n: number): number[] { - let factors: number[] = []; - - let target = n; - for (let i = 2; target !== 1; i = i === 2 ? 3 : i + 2) { - while (target % i === 0) { - factors.push(i); - target = target / i; - } - } - - return factors; -} - -function draw() { - const canvas = $("#canvas"); - const ctx = canvas.getContext("2d")!; - - function clearCanvas(ctx: CanvasRenderingContext2D): void { - ctx.save(); - ctx.fillStyle = "#ffffff"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - } - - clearCanvas(ctx); - - ctx.save(); - ctx.beginPath(); - ctx.strokeStyle = "#7b7b7b"; - for (let x = 0; x <= width; x++) { - ctx.moveTo(x * scale, 0); - ctx.lineTo(x * scale, height * scale); - } - for (let y = 0; y <= height; y++) { - ctx.moveTo(0, y * scale); - ctx.lineTo(width * scale, y * scale); - } - ctx.stroke(); - ctx.restore(); - - ctx.save(); - ctx.fillStyle = "#ff0000"; - for (let i = 1; i <= width * height; i++) { - if (isPrime(i)) - ctx.fillRect(((i - 1) % width) * scale, Math.floor((i - 1) / width) * scale, scale, scale); - } - ctx.restore(); -} - doAfterLoad(async () => { const canvas = $("#canvas"); canvas.classList.remove("invisible"); - canvas.width = scale * width; - canvas.height = scale * height; - { - const cb = () => { - draw(); - window.requestAnimationFrame(cb); - }; - window.requestAnimationFrame(cb); - } + const display = new Display(canvas); + display.startDrawLoop(); }); diff --git a/src/main/js/PrimeMath.ts b/src/main/js/PrimeMath.ts new file mode 100644 index 0000000..bc239fa --- /dev/null +++ b/src/main/js/PrimeMath.ts @@ -0,0 +1,92 @@ +export interface PrimeMath { + isPrime(n: number): boolean + + decompose(n: number): number[] +} + +export class SimplePrimeMath implements PrimeMath { + private static instance: SimplePrimeMath; + + + private constructor() { + // Do nothing + } + + public static getInstance(): SimplePrimeMath { + if (!SimplePrimeMath.instance) + SimplePrimeMath.instance = new SimplePrimeMath(); + + return SimplePrimeMath.instance; + } + + + public isPrime(n: number): boolean { + if (!Number.isInteger(n)) throw Error("Cannot check non-integer number for primality."); + + if (n < 2) return false; + if (n === 2) return true; + if (n % 2 === 0) return false; + + const limit = Math.floor(Math.sqrt(n)); + for (let i = 3; i <= limit; i += 2) + if (n % i === 0) + return false; + + return true; + } + + public decompose(n: number): number[] { + if (!Number.isInteger(n)) throw Error("Cannot find prime decomposition of non-integer number."); + + let factors: number[] = []; + + let target = n; + for (let i = 2; target !== 1; i = i === 2 ? 3 : i + 2) { + while (target % i === 0) { + factors.push(i); + target = target / i; + } + } + + return factors; + } +} + +export class CachedPrimeMath implements PrimeMath { + private static instance: CachedPrimeMath; + private static simple: SimplePrimeMath = SimplePrimeMath.getInstance(); + + private isPrimeCache: Map = new Map(); + private decomposeCache: Map = new Map(); + + + private constructor() { + // Do nothing + } + + public static getInstance(): CachedPrimeMath { + if (!CachedPrimeMath.instance) + CachedPrimeMath.instance = new CachedPrimeMath(); + + return CachedPrimeMath.instance; + } + + + public isPrime(n: number): boolean { + return CachedPrimeMath.invokeCached(n, CachedPrimeMath.simple.isPrime, this.isPrimeCache); + } + + public decompose(n: number): number[] { + return CachedPrimeMath.invokeCached(n, CachedPrimeMath.simple.decompose, this.decomposeCache); + } + + + private static invokeCached(input: IN, fun: (_: IN) => OUT, cache: Map): OUT { + if (cache.has(input)) + return cache.get(input)!!; + + const output = fun(input); + cache.set(input, output); + return output; + } +}