Add more utils
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'bun:test';
|
import { describe, expect, it } from 'bun:test';
|
||||||
import { solveFirst } from './index.ts';
|
import { solveFirst, solveSecond } from './index.ts';
|
||||||
|
|
||||||
describe('2024-06', () => {
|
describe('2024-06', () => {
|
||||||
const testInput = `....#.....
|
const testInput = `....#.....
|
||||||
@@ -16,7 +16,10 @@ describe('2024-06', () => {
|
|||||||
expect(solveFirst(testInput)).toBe(41);
|
expect(solveFirst(testInput)).toBe(41);
|
||||||
});
|
});
|
||||||
it('second', () => {
|
it('second', () => {
|
||||||
// that takes too long
|
// it takes too long
|
||||||
// expect(solveSecond(testInput)).toBe(6);
|
// so only run while `NODE_ENV=production bun test --coverage`
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
expect(solveSecond(testInput)).toBe(6);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-8
@@ -1,8 +1,4 @@
|
|||||||
import { sumBy } from '../../utils/array.ts';
|
import { replaceChar, sumBy } from '../../utils/array.ts';
|
||||||
|
|
||||||
export function replace(arr: string[], x: number, y: number, char: string) {
|
|
||||||
arr[y] = arr[y].substring(0, x) + char + arr[y].substring(x + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const directions = [
|
const directions = [
|
||||||
{ d: '^', x: 0, y: -1 },
|
{ d: '^', x: 0, y: -1 },
|
||||||
@@ -28,7 +24,7 @@ export function solveFirst(input: string): number {
|
|||||||
direction = (direction + 1) % 4;
|
direction = (direction + 1) % 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(rows, x, y, 'X');
|
replaceChar(rows, x, y, 'X');
|
||||||
x += directions[direction].x;
|
x += directions[direction].x;
|
||||||
y += directions[direction].y;
|
y += directions[direction].y;
|
||||||
}
|
}
|
||||||
@@ -98,12 +94,12 @@ export function solveRow(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(rows, column, row, '#');
|
replaceChar(rows, column, row, '#');
|
||||||
if (!finishes(rows, startX, startY, startDirection)) {
|
if (!finishes(rows, startX, startY, startDirection)) {
|
||||||
found++;
|
found++;
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(rows, column, row, '.');
|
replaceChar(rows, column, row, '.');
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,8 @@
|
|||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"check": "prettier --check .",
|
"check": "prettier --check .",
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
"test:dev": "bun test --watch"
|
"test:dev": "bun test --watch 2024",
|
||||||
|
"test:utils": "NODE_ENV=production bun test --watch --coverage utils/*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
|||||||
+82
-1
@@ -1,10 +1,14 @@
|
|||||||
import { describe, expect, it } from 'bun:test';
|
import { describe, expect, it } from 'bun:test';
|
||||||
import {
|
import {
|
||||||
|
combinations,
|
||||||
getMiddleElement,
|
getMiddleElement,
|
||||||
|
groupBy,
|
||||||
multArray,
|
multArray,
|
||||||
multBy,
|
multBy,
|
||||||
|
replaceChar,
|
||||||
sumArray,
|
sumArray,
|
||||||
sumBy
|
sumBy,
|
||||||
|
unique
|
||||||
} from './array.ts';
|
} from './array.ts';
|
||||||
|
|
||||||
describe('array utils', () => {
|
describe('array utils', () => {
|
||||||
@@ -133,10 +137,87 @@ describe('array utils', () => {
|
|||||||
expect(multBy([], (e) => e)).toBeFinite();
|
expect(multBy([], (e) => e)).toBeFinite();
|
||||||
expect(multBy([], (e) => e)).toBe(0);
|
expect(multBy([], (e) => e)).toBe(0);
|
||||||
});
|
});
|
||||||
|
it('groupBy', () => {
|
||||||
|
const arr = [0, 1, 2, 2, 1, 3];
|
||||||
|
const actual = groupBy(arr, (e) => e);
|
||||||
|
const expected = [
|
||||||
|
{ key: 0, elements: [0] },
|
||||||
|
{ key: 1, elements: [1, 1] },
|
||||||
|
{ key: 2, elements: [2, 2] },
|
||||||
|
{ key: 3, elements: [3] }
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(actual).toHaveLength(actual.length);
|
||||||
|
expect(Object.keys(actual.map((e) => e.key))).toEqual(
|
||||||
|
expect.arrayContaining(Object.keys(expected.map((e) => e.key)))
|
||||||
|
);
|
||||||
|
expected.forEach(({ key, elements }) => {
|
||||||
|
const actualGroup = actual.find((e) => e.key === key);
|
||||||
|
expect(actualGroup).toBeTruthy();
|
||||||
|
expect(actualGroup).toBeObject();
|
||||||
|
expect(actualGroup?.elements || []).toHaveLength(elements.length);
|
||||||
|
expect(actualGroup?.elements || []).toEqual(
|
||||||
|
expect.arrayContaining(elements)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
it('getMiddleElement', () => {
|
it('getMiddleElement', () => {
|
||||||
expect(getMiddleElement([1, 2, 3, 4])).toBe(3);
|
expect(getMiddleElement([1, 2, 3, 4])).toBe(3);
|
||||||
expect(getMiddleElement([1, 2, 3])).toBe(2);
|
expect(getMiddleElement([1, 2, 3])).toBe(2);
|
||||||
expect(getMiddleElement([1])).toBe(1);
|
expect(getMiddleElement([1])).toBe(1);
|
||||||
expect(getMiddleElement([])).toBeUndefined();
|
expect(getMiddleElement([])).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
it('combinations', () => {
|
||||||
|
const numbers = [0, 1, 2];
|
||||||
|
const n = numbers.length;
|
||||||
|
const actual = combinations(numbers);
|
||||||
|
const expected = [
|
||||||
|
{ a: 0, b: 1 },
|
||||||
|
{ a: 0, b: 2 },
|
||||||
|
{ a: 1, b: 0 },
|
||||||
|
{ a: 1, b: 2 },
|
||||||
|
{ a: 2, b: 0 },
|
||||||
|
{ a: 2, b: 1 }
|
||||||
|
];
|
||||||
|
expect(actual).toHaveLength(n * (n - 1));
|
||||||
|
expect(actual).toEqual(expect.arrayContaining(expected));
|
||||||
|
expect(expected).toEqual(expect.arrayContaining(actual));
|
||||||
|
});
|
||||||
|
it('unique numbers', () => {
|
||||||
|
const arr = [0, 1, 2, 2, 1, 3];
|
||||||
|
const actual = arr.filter(unique());
|
||||||
|
const expected = [0, 1, 2, 3];
|
||||||
|
|
||||||
|
expect(actual).toHaveLength(actual.length);
|
||||||
|
expect(actual).toEqual(expect.arrayContaining(expected));
|
||||||
|
expect(expected).toEqual(expect.arrayContaining(actual));
|
||||||
|
});
|
||||||
|
it('unique objects', () => {
|
||||||
|
const arr = [{ a: 0 }, { a: 1 }, { a: 2 }, { a: 2 }, { a: 1 }, { a: 3 }];
|
||||||
|
const actual = arr.filter(unique((a, b) => a.a === b.a));
|
||||||
|
const expected = [{ a: 0 }, { a: 1 }, { a: 2 }, { a: 3 }];
|
||||||
|
|
||||||
|
expect(actual).toHaveLength(actual.length);
|
||||||
|
expect(actual).toEqual(expect.arrayContaining(expected));
|
||||||
|
expect(expected).toEqual(expect.arrayContaining(actual));
|
||||||
|
|
||||||
|
// should just pass everything through since objects equality check is by reference
|
||||||
|
expect(arr.filter(unique())).toHaveLength(arr.length);
|
||||||
|
});
|
||||||
|
it('replaceChar', () => {
|
||||||
|
const input = `000000
|
||||||
|
111111
|
||||||
|
222222
|
||||||
|
333333`;
|
||||||
|
const rows = input.split('\n');
|
||||||
|
replaceChar(rows, 1, 2, 'b');
|
||||||
|
const actual = rows.join('\n');
|
||||||
|
const expected = `000000
|
||||||
|
111111
|
||||||
|
2b2222
|
||||||
|
333333`;
|
||||||
|
|
||||||
|
expect(actual).toHaveLength(expected.length);
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -90,3 +90,50 @@ export function groupBy<E, K>(
|
|||||||
export function getMiddleElement<E>(arr: ReadonlyArray<E>) {
|
export function getMiddleElement<E>(arr: ReadonlyArray<E>) {
|
||||||
return arr[Math.floor(arr.length / 2)];
|
return arr[Math.floor(arr.length / 2)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with every combination of every element in an array.
|
||||||
|
* The combination array will have a length of `n * (n-1)` for an array of length `n`.
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const arr = [0, 1, 2]
|
||||||
|
*
|
||||||
|
* combinations(arr) === [
|
||||||
|
* {a: 0, b: 1},
|
||||||
|
* {a: 0, b: 2},
|
||||||
|
* {a: 1, b: 0},
|
||||||
|
* {a: 1, b: 2},
|
||||||
|
* {a: 2, b: 0},
|
||||||
|
* {a: 2, b: 1},
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
* @param arr
|
||||||
|
*/
|
||||||
|
export function combinations<E>(arr: ReadonlyArray<E>): { a: E; b: E }[] {
|
||||||
|
return arr
|
||||||
|
.flatMap((a, _, arr) => arr.flatMap((b) => (a !== b ? [{ a, b }] : null)))
|
||||||
|
.filter((c) => !!c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a filter lambda to be used in array.filter() to only allow the
|
||||||
|
* first occurrence of duplicate values.
|
||||||
|
* @param isEqual Custom equality check, defaults to `a === b`
|
||||||
|
*/
|
||||||
|
export function unique<E>(
|
||||||
|
isEqual: (a: E, b: E) => boolean = (a, b) => a === b
|
||||||
|
) {
|
||||||
|
return (self: E, index: number, arr: ReadonlyArray<E>) =>
|
||||||
|
arr.findIndex((v) => isEqual(v, self)) === index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a char in a two-dimensional array of strings.
|
||||||
|
* @param arr two-dimensional string array
|
||||||
|
* @param x column
|
||||||
|
* @param y row
|
||||||
|
* @param char Char
|
||||||
|
*/
|
||||||
|
export function replaceChar(arr: string[], x: number, y: number, char: string) {
|
||||||
|
arr[y] = arr[y].substring(0, x) + char + arr[y].substring(x + 1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, expect, it } from 'bun:test';
|
||||||
|
import { type Coordinate, inBounds, isCoordinateEqual } from './coordinates.ts';
|
||||||
|
|
||||||
|
describe('coordinates utils', () => {
|
||||||
|
it('inBounds', () => {
|
||||||
|
expect(inBounds(10, 10)([0, 0])).toBeTrue();
|
||||||
|
expect(inBounds(10, 10)([9, 3])).toBeTrue();
|
||||||
|
expect(inBounds(10, 10)([9, 9])).toBeTrue();
|
||||||
|
expect(inBounds(10, 10)([10, 9])).toBeFalse();
|
||||||
|
expect(inBounds(10, 10)([10, 0])).toBeFalse();
|
||||||
|
expect(inBounds(10, 10)([-1, 0])).toBeFalse();
|
||||||
|
expect(inBounds(10, 10)([-0.1, 0])).toBeFalse();
|
||||||
|
expect(inBounds(10, 10)([0, 10000])).toBeFalse();
|
||||||
|
expect(inBounds(10, 10)([10000, 10000])).toBeFalse();
|
||||||
|
expect(inBounds(10, 10, 5, 5)([0, 0])).toBeFalse();
|
||||||
|
expect(inBounds(10, 10, 5, 0)([5, 0])).toBeTrue();
|
||||||
|
expect(inBounds(10, 10, 5, 0)([4, 0])).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inBounds', () => {
|
||||||
|
expect(isCoordinateEqual([0, 0], [0, 0])).toBeTrue();
|
||||||
|
expect(isCoordinateEqual([0, 1], [0, 1])).toBeTrue();
|
||||||
|
expect(isCoordinateEqual([0, 1], [1, 0])).toBeFalse();
|
||||||
|
expect(isCoordinateEqual([1 + 2, 3 + 4], [3, 7])).toBeTrue();
|
||||||
|
|
||||||
|
const a = [0, 0] as const;
|
||||||
|
expect(isCoordinateEqual(a, a)).toBeTrue();
|
||||||
|
|
||||||
|
const b: Coordinate = [0, 0];
|
||||||
|
expect(isCoordinateEqual(b, b)).toBeTrue();
|
||||||
|
expect(isCoordinateEqual(a, b)).toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export type Coordinate = [number, number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a filter lambda to be used in array.filter() on a xy-Coordinate array (array of two number arrays).
|
||||||
|
* @param xMax Exclusive
|
||||||
|
* @param yMax Exclusive
|
||||||
|
* @param xMin Inclusive, defaults to 0
|
||||||
|
* @param yMin Inclusive, defaults to 0
|
||||||
|
*/
|
||||||
|
export function inBounds(xMax: number, yMax: number, xMin = 0, yMin = 0) {
|
||||||
|
return ([x, y]: Readonly<Coordinate>) =>
|
||||||
|
x >= xMin && x < xMax && y >= yMin && y < yMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the equality of two xy-coordinates. Checks both x and y independently for number equality.
|
||||||
|
* @param a xy-Coordinate, two number array
|
||||||
|
* @param b xy-Coordinate, two number array
|
||||||
|
*/
|
||||||
|
export function isCoordinateEqual(
|
||||||
|
a: Readonly<Coordinate>,
|
||||||
|
b: Readonly<Coordinate>
|
||||||
|
): boolean {
|
||||||
|
return b[0] === a[0] && b[1] === a[1];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user