Refactor (1)

This commit is contained in:
2025-09-19 18:54:12 +09:00
committed by cannorin
parent 6b15542c40
commit c3b1bf39a4
26 changed files with 1684 additions and 105 deletions

View File

@@ -0,0 +1,3 @@
{
"extends": ["../../biome.json"]
}

1
packages/utils/index.ts Normal file
View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,22 @@
{
"name": "@cannorin/utils",
"version": "0.0.1",
"private": true,
"exports": {
".": "./index.ts",
"./headless": "./src/headless.ts",
"./array": "./src/array.ts",
"./vector": "./src/vector.ts",
"./multiset": "./src/multiset.ts"
},
"scripts": {
"build": "tsc",
"lint": "biome check",
"fix": "biome check --fix",
"test": "vitest"
},
"devDependencies": {
"typescript": "5.7.3",
"vitest": "3.2.4"
}
}

View File

@@ -0,0 +1,12 @@
export const sample = <T>(arr: readonly T[]): T =>
arr[Math.floor(Math.random() * arr.length)] as T;
export function sampleMany<T>(arr: T[], n: number = arr.length): T[] {
if (n > arr.length) return sampleMany(arr, arr.length);
const copy = [...arr];
for (let i = 0; i < n; i++) {
const j = i + Math.floor(Math.random() * (copy.length - i));
[copy[i], copy[j]] = [copy[j] as T, copy[i] as T];
}
return copy.slice(0, n);
}

View File

@@ -0,0 +1,40 @@
export {};
declare global {
interface String {
startsWith<S extends string>(searchString: S): this is `${S}${string}`;
endsWith<S extends string>(searchString: S): this is `${string}${S}`;
includes<S extends string>(
searchString: S,
position?: number,
): this is `${string}${S}${string}`;
}
type LiteralUnionLike<T> = T extends string
? T extends ""
? T
: T extends `${T}${T}`
? never
: T
: T extends number
? `${T}0` extends `${number}`
? T
: never
: T extends null | undefined
? T
: never;
interface Array<T> {
includes(
searchElement: T extends LiteralUnionLike<T> ? unknown : never,
fromIndex?: number,
): searchElement is T extends LiteralUnionLike<T> ? T : never;
}
interface ReadonlyArray<T> {
includes(
searchElement: T extends LiteralUnionLike<T> ? unknown : never,
fromIndex?: number,
): searchElement is T extends LiteralUnionLike<T> ? T : never;
}
}

View File

@@ -0,0 +1,54 @@
export class MultiSet<T> extends Map<T, number> {
constructor(iterable?: Iterable<readonly [T, number]>) {
super();
if (iterable) {
for (const [item, count] of iterable) {
this.add(item, count);
}
}
}
/**
* Adds the specified item to the multiset.
* If the item already exists, its count is incremented.
*
* @param item The element to add.
* @param count The number of times to add the element (default is 1).
* @returns The current instance for chaining.
*/
add(item: T, count = 1): this {
const currentCount = this.get(item) ?? 0;
this.set(item, currentCount + count);
return this;
}
/**
* Removes one or more instances of the specified item.
*
* @param item The element to remove.
* @param count The number of times to remove the element (default is 1).
* @returns True if the element was present, false otherwise.
*/
remove(item: T, count = 1): boolean {
const currentCount = this.get(item);
if (currentCount === undefined) {
return false;
}
if (currentCount <= count) {
this.delete(item);
} else {
this.set(item, currentCount - count);
}
return true;
}
/**
* Retrieves the count of the specified item.
*
* @param item The element whose count is to be retrieved.
* @returns The number of occurrences of the element.
*/
count(item: T): number {
return this.get(item) ?? 0;
}
}

View File

@@ -0,0 +1,28 @@
export type Vector = {
x: number;
y: number;
};
export type Radian = number;
export const degree = (value: number) => ((value / 180) * Math.PI) as Radian;
export const sub = (v1: Vector, v2: Vector) =>
({
x: v1.x - v2.x,
y: v1.y - v2.y,
}) as Vector;
export const add = (v1: Vector, v2: Vector) =>
({
x: v1.x + v2.x,
y: v1.y + v2.y,
}) as Vector;
export const theta = ({ x, y }: Vector) => Math.atan2(y, x) as Radian;
export const rotate = ({ x, y }: Vector, rad: Radian) =>
({
x: x * Math.cos(rad) - y * Math.sin(rad),
y: x * Math.sin(rad) + y * Math.cos(rad),
}) as Vector;

View File

@@ -0,0 +1,12 @@
{
"extends": "@tsconfig/strictest/tsconfig.json",
"compilerOptions": {
"target": "esnext",
"lib": ["esnext"],
"module": "esnext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"noEmit": true,
"allowSyntheticDefaultImports": true
}
}