prime-map-generator/src/main/js/PrimeMath.ts

170 lines
4.2 KiB
TypeScript

/**
* Exposes maths functions for prime numbers.
*/
export interface PrimeMath {
/**
* Checks a number for primality.
*
* @param n the number to check for primality
* @return `true` if and only if `n` is prime
*/
isPrime(n: number): boolean
/**
* Decomposes `n` into its prime factors.
*
* @param n the number to decompose
* @return the array of prime factors of `n`
*/
decompose(n: number): number[]
}
/**
* Simple implementation of `PrimeMath`.
*/
export class SimplePrimeMath implements PrimeMath {
/**
* The singleton instance.
*
* @private
*/
private static instance: SimplePrimeMath;
/**
* Constructs a new `SimplePrimeMath`.
*
* @private
*/
private constructor() {
// Do nothing
}
/**
* Returns the singleton instance.
*
* @return the singleton instance
*/
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;
}
}
/**
* Implements `PrimeMath` such that all queries are cached so that subsequent queries are faster.
*/
export class CachedPrimeMath implements PrimeMath {
/**
* The singleton instance.
*
* @private
*/
private static instance: CachedPrimeMath;
/**
* The underlying `SimplePrimeMath` instance of which the answers are cached.
*
* @private
*/
private static simple: SimplePrimeMath = SimplePrimeMath.getInstance();
/**
* Cached outputs for `isPrime` invocations.
*
* @private
*/
private isPrimeCache: Map<number, boolean> = new Map();
/**
* Cached outputs for `decomposeCache` invocations.
*
* @private
*/
private decomposeCache: Map<number, number[]> = new Map();
/**
* Constructs a new instance.
*
* @private
*/
private constructor() {
// Do nothing
}
/**
* Returns the singleton instance.
*
* @return the singleton instance
*/
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);
}
/**
* Invokes `fun` given `input`, and stores its output in `cache`.
*
* If `fun(input)` has previously been invoked, then the previous output is returned immediately.
*
* @param input the input to pass to `fun`
* @param fun the function to invoke with `input`
* @param cache the cache to store the output of `fun` in, and to read previous outputs from
* @return the output of `fun(input)`, or the previously stored output if there is one
* @private
*/
private static invokeCached<IN, OUT>(input: IN, fun: (_: IN) => OUT, cache: Map<IN, OUT>): OUT {
if (cache.has(input))
return cache.get(input)!!;
const output = fun(input);
cache.set(input, output);
return output;
}
}