Add solution day 13
This commit is contained in:
parent
b4f98c1ea3
commit
cfc8c7bb66
15
day_13/example.txt
Normal file
15
day_13/example.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Button A: X+94, Y+34
|
||||||
|
Button B: X+22, Y+67
|
||||||
|
Prize: X=8400, Y=5400
|
||||||
|
|
||||||
|
Button A: X+26, Y+66
|
||||||
|
Button B: X+67, Y+21
|
||||||
|
Prize: X=12748, Y=12176
|
||||||
|
|
||||||
|
Button A: X+17, Y+86
|
||||||
|
Button B: X+84, Y+37
|
||||||
|
Prize: X=7870, Y=6450
|
||||||
|
|
||||||
|
Button A: X+69, Y+23
|
||||||
|
Button B: X+27, Y+71
|
||||||
|
Prize: X=18641, Y=10279
|
1279
day_13/input.txt
Normal file
1279
day_13/input.txt
Normal file
File diff suppressed because it is too large
Load Diff
211
day_13/solve_1.ts
Normal file
211
day_13/solve_1.ts
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
/**
|
||||||
|
* Calculate the greatest common divisor of two numbers using the Euclidean algorithm
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
* @returns gcd of a and b and the coefficients s and t such that gcd(a, b) = s*a + t*b
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function extended_euclidean_algorithm(a: number, b: number) {
|
||||||
|
let [old_r, r] = [a, b];
|
||||||
|
let [old_s, s] = [1, 0];
|
||||||
|
let [old_t, t] = [0, 1];
|
||||||
|
|
||||||
|
while (r !== 0) {
|
||||||
|
const q = Math.floor(old_r / r);
|
||||||
|
[old_r, r] = [r, old_r - q * r];
|
||||||
|
[old_s, s] = [s, old_s - q * s];
|
||||||
|
[old_t, t] = [t, old_t - q * t];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { gcd: old_r, s: old_s, t: old_t };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* solve ax + by = c
|
||||||
|
* if solution exists, return {x, y, d} where d = gcd(a, b)
|
||||||
|
* if no solution exists, return null
|
||||||
|
* @param {number} a
|
||||||
|
* @param {number} b
|
||||||
|
* @param {number} c
|
||||||
|
*/
|
||||||
|
export function diopantos_solve(
|
||||||
|
a: number,
|
||||||
|
b: number,
|
||||||
|
c: number,
|
||||||
|
) {
|
||||||
|
const { gcd, s, t } = extended_euclidean_algorithm(a, b);
|
||||||
|
|
||||||
|
if (c % gcd !== 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = s * (c / gcd);
|
||||||
|
const y = t * (c / gcd);
|
||||||
|
|
||||||
|
return { x, y, d: gcd };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Vector2 = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClawMachineData = {
|
||||||
|
buttonA: Vector2;
|
||||||
|
buttonB: Vector2;
|
||||||
|
prize: Vector2;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse input data:
|
||||||
|
* Button A: X+94, Y+34
|
||||||
|
* Button B: X+22, Y+67
|
||||||
|
* Prize: X=8400, Y=5400
|
||||||
|
*/
|
||||||
|
export async function readData(path: string): Promise<ClawMachineData[]> {
|
||||||
|
const text = await Deno.readTextFile(path);
|
||||||
|
// TODO: support windows line endings
|
||||||
|
const objects = text.split("\n\n"); // split by empty line.
|
||||||
|
return objects.map((object) => {
|
||||||
|
const lines = object.split("\n");
|
||||||
|
const buttonA = lines[0].match(/X\+(\d+), Y\+(\d+)/);
|
||||||
|
const buttonB = lines[1].match(/X\+(\d+), Y\+(\d+)/);
|
||||||
|
const prize = lines[2].match(/X=(\d+), Y=(\d+)/);
|
||||||
|
|
||||||
|
if (!buttonA || !buttonB || !prize) {
|
||||||
|
throw new Error("Invalid input format");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buttonA: { x: parseInt(buttonA[1]), y: parseInt(buttonA[2]) },
|
||||||
|
buttonB: { x: parseInt(buttonB[1]), y: parseInt(buttonB[2]) },
|
||||||
|
prize: { x: parseInt(prize[1]), y: parseInt(prize[2]) },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solution space for the diophantine equation ax + by = c
|
||||||
|
*/
|
||||||
|
export type SolutionSpace = {
|
||||||
|
/**
|
||||||
|
* solution space origin
|
||||||
|
*/
|
||||||
|
origin: Vector2;
|
||||||
|
/**
|
||||||
|
* difference between two solutions
|
||||||
|
* you can get other solutions by adding this vector to the origin
|
||||||
|
*/
|
||||||
|
dir: Vector2;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class NoSolutionError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("No solution found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find the solution space for the diophantine equation ax + by = c
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
* @param c
|
||||||
|
* @returns solution space
|
||||||
|
*/
|
||||||
|
export function find_solution_space(
|
||||||
|
a: number,
|
||||||
|
b: number,
|
||||||
|
c: number,
|
||||||
|
): SolutionSpace {
|
||||||
|
const result = diopantos_solve(a, b, c);
|
||||||
|
if (!result) {
|
||||||
|
throw new NoSolutionError();
|
||||||
|
}
|
||||||
|
const { x, y, d } = result;
|
||||||
|
const k = Math.ceil(-x / (b / d));
|
||||||
|
return {
|
||||||
|
origin: { x: x + k * (b / d), y: y - k * (a / d) },
|
||||||
|
dir: { x: b / d, y: -a / d },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function make_other_solution(
|
||||||
|
space: SolutionSpace,
|
||||||
|
k: number,
|
||||||
|
): { x: number; y: number } {
|
||||||
|
return {
|
||||||
|
x: space.origin.x + k * space.dir.x,
|
||||||
|
y: space.origin.y + k * space.dir.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function make_all_positive_solution(
|
||||||
|
space: SolutionSpace,
|
||||||
|
): { x: number; y: number }[] {
|
||||||
|
const solutions = [];
|
||||||
|
const k_begin = Math.floor(space.origin.x / space.dir.x);
|
||||||
|
const k_end = Math.floor(-space.origin.y / space.dir.y);
|
||||||
|
for (let k = k_begin; k <= k_end; k++) {
|
||||||
|
const solution = make_other_solution(space, k);
|
||||||
|
if (solution.x >= 0 && solution.y >= 0) {
|
||||||
|
solutions.push(solution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return solutions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function check_solution(data: ClawMachineData, x: number, y: number) {
|
||||||
|
return data.buttonA.x * x + data.buttonB.x * y === data.prize.x &&
|
||||||
|
data.buttonA.y * x + data.buttonB.y * y === data.prize.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.main) {
|
||||||
|
const costA = 3;
|
||||||
|
const costB = 1;
|
||||||
|
|
||||||
|
const data = await readData("input.txt");
|
||||||
|
let sum = 0;
|
||||||
|
data.forEach((clawMachineData) => {
|
||||||
|
// solution space is the intersection of the two solutions
|
||||||
|
// find the minimum cost to reach the prize
|
||||||
|
// cost = costA * x + costB * y
|
||||||
|
try {
|
||||||
|
const space = find_solution_space(
|
||||||
|
clawMachineData.buttonA.x,
|
||||||
|
clawMachineData.buttonB.x,
|
||||||
|
clawMachineData.prize.x,
|
||||||
|
);
|
||||||
|
const space2 = find_solution_space(
|
||||||
|
clawMachineData.buttonA.y,
|
||||||
|
clawMachineData.buttonB.y,
|
||||||
|
clawMachineData.prize.y,
|
||||||
|
);
|
||||||
|
const solutions = make_all_positive_solution(space);
|
||||||
|
const solutions2 = make_all_positive_solution(space2);
|
||||||
|
|
||||||
|
const intersection = solutions.filter((solution) => {
|
||||||
|
return solutions2.some((solution2) => {
|
||||||
|
return solution.x === solution2.x &&
|
||||||
|
solution.y === solution2.y;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(intersection);
|
||||||
|
if (intersection.length === 0) {
|
||||||
|
throw new NoSolutionError();
|
||||||
|
}
|
||||||
|
const minCost = Math.min(...intersection.map((solution) => {
|
||||||
|
return costA * solution.x + costB * solution.y;
|
||||||
|
}));
|
||||||
|
console.log(minCost);
|
||||||
|
sum += minCost;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof NoSolutionError) {
|
||||||
|
console.log("No solution found");
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(sum);
|
||||||
|
}
|
74
day_13/solve_2.ts
Normal file
74
day_13/solve_2.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { readData, NoSolutionError } from "./solve_1.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param coefficients
|
||||||
|
* @param constants
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function solve_linear_equation(
|
||||||
|
coefficients: readonly [
|
||||||
|
readonly [number, number],
|
||||||
|
readonly [number, number],
|
||||||
|
],
|
||||||
|
constants: readonly [number, number],
|
||||||
|
): [number, number] {
|
||||||
|
const [[a, b], [c, d]] = coefficients;
|
||||||
|
const [e, f] = constants;
|
||||||
|
const det = a * d - b * c;
|
||||||
|
if (det === 0) {
|
||||||
|
throw new NoSolutionError();
|
||||||
|
}
|
||||||
|
if ((e * d - b * f) % det != 0 || (a * f - e * c) % det != 0) {
|
||||||
|
throw new NoSolutionError();
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
(e * d - b * f) / det,
|
||||||
|
(a * f - e * c) / det,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.main) {
|
||||||
|
const costA = 3;
|
||||||
|
const costB = 1;
|
||||||
|
|
||||||
|
const data = await readData("input.txt");
|
||||||
|
data.forEach((clawMachineData) => {
|
||||||
|
clawMachineData.prize.x += 10000000000000;
|
||||||
|
clawMachineData.prize.y += 10000000000000;
|
||||||
|
});
|
||||||
|
let sum = 0;
|
||||||
|
data.forEach((clawMachineData) => {
|
||||||
|
// solution space is the intersection of the two solutions
|
||||||
|
// find the minimum cost to reach the prize
|
||||||
|
// cost = costA * x + costB * y
|
||||||
|
console.log("----");
|
||||||
|
try {
|
||||||
|
const coefficients = [
|
||||||
|
[clawMachineData.buttonA.x, clawMachineData.buttonB.x],
|
||||||
|
[clawMachineData.buttonA.y, clawMachineData.buttonB.y],
|
||||||
|
] as const;
|
||||||
|
const constants = [clawMachineData.prize.x, clawMachineData.prize.y] as const;
|
||||||
|
const [x, y] = solve_linear_equation(coefficients, constants);
|
||||||
|
if (x < 0 || y < 0) {
|
||||||
|
console.log("Negative solution");
|
||||||
|
throw new NoSolutionError();
|
||||||
|
}
|
||||||
|
console.log("Solution:", x, y);
|
||||||
|
const cost = costA * x + costB * y;
|
||||||
|
console.log("Cost:", cost);
|
||||||
|
sum += cost;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof NoSolutionError) {
|
||||||
|
console.log("No solution found");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
console.log("----");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(sum);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user