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