Solution to 13-2
This commit is contained in:
parent
e45052d14a
commit
956109235d
1279
inputs/13/real.txt
Normal file
1279
inputs/13/real.txt
Normal file
File diff suppressed because it is too large
Load Diff
15
inputs/13/test.txt
Normal file
15
inputs/13/test.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
|
@ -2,6 +2,8 @@ const std = @import("std");
|
|||||||
const print = std.debug.print;
|
const print = std.debug.print;
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
// Part Two seems just plain unfun. _Might_ come back to it, but...ugh.
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
const response = try part_one(false);
|
const response = try part_one(false);
|
||||||
print("{}\n", .{response});
|
print("{}\n", .{response});
|
||||||
|
274
solutions/13.zig
Normal file
274
solutions/13.zig
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
const util = @import("util.zig");
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
const response = try part_two(false);
|
||||||
|
print("{}\n", .{response});
|
||||||
|
}
|
||||||
|
|
||||||
|
const CaseError = error{ ArithmeticError, InsolubleError, ZeroDivisionError };
|
||||||
|
|
||||||
|
const Solution = struct { a: u32, b: u32 };
|
||||||
|
const SolutionTakeTwo = struct { a: u64, b: u64 };
|
||||||
|
|
||||||
|
const Case = struct {
|
||||||
|
ax: u32,
|
||||||
|
ay: u32,
|
||||||
|
bx: u32,
|
||||||
|
by: u32,
|
||||||
|
prizex: u32,
|
||||||
|
prizey: u32,
|
||||||
|
pub fn from_lines(a_line: []const u8, b_line: []const u8, prize_line: []const u8) anyerror!Case {
|
||||||
|
// Looks like there are no regexes in Zig :shrug:
|
||||||
|
print("DEBUG - parsing a_line: {s}\n", .{a_line});
|
||||||
|
const start_of_ax: usize = 12;
|
||||||
|
const end_of_ax: usize = std.mem.indexOf(u8, a_line, ",").?;
|
||||||
|
const start_of_ay: usize = std.mem.indexOf(u8, a_line, "Y").? + 2;
|
||||||
|
const ax = try std.fmt.parseInt(u32, a_line[start_of_ax..end_of_ax], 10);
|
||||||
|
const ay = try std.fmt.parseInt(u32, a_line[start_of_ay..a_line.len], 10);
|
||||||
|
|
||||||
|
const start_of_bx: usize = 12;
|
||||||
|
const end_of_bx: usize = std.mem.indexOf(u8, b_line, ",").?;
|
||||||
|
const start_of_by: usize = std.mem.indexOf(u8, b_line, "Y").? + 2;
|
||||||
|
const bx = try std.fmt.parseInt(u32, b_line[start_of_bx..end_of_bx], 10);
|
||||||
|
const by = try std.fmt.parseInt(u32, b_line[start_of_by..b_line.len], 10);
|
||||||
|
|
||||||
|
const start_of_prizex: usize = 9;
|
||||||
|
const end_of_prizex: usize = std.mem.indexOf(u8, prize_line, ",").?;
|
||||||
|
const start_of_prizey: usize = std.mem.indexOf(u8, prize_line, "Y").? + 2;
|
||||||
|
const prizex = try std.fmt.parseInt(u32, prize_line[start_of_prizex..end_of_prizex], 10);
|
||||||
|
const prizey = try std.fmt.parseInt(u32, prize_line[start_of_prizey..prize_line.len], 10);
|
||||||
|
|
||||||
|
return Case{ .ax = ax, .ay = ay, .bx = bx, .by = by, .prizex = prizex, .prizey = prizey };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve(self: *const Case) CaseError!Solution {
|
||||||
|
// Logic:
|
||||||
|
// * Increment b (since b is cheaper than a) until x and y values are >= targets
|
||||||
|
// * If x and y values are equal to the targets, _and_ b <= 100, then we're done - return
|
||||||
|
// * Repeatedly:
|
||||||
|
// * Increment a, then decrement b until _at least one_ x or y value is <= targets
|
||||||
|
// * If both x and y are equal to targets, and b <= 100, then return
|
||||||
|
// * Else:
|
||||||
|
// * Increment b until both x and y are >= targets
|
||||||
|
// * If a is 100, the Case is insoluble. Else, loop (so a will get incremented as the loop starts again)
|
||||||
|
|
||||||
|
var a: u32 = 0;
|
||||||
|
var b: u32 = 0;
|
||||||
|
b = @max(self.prizex / self.bx, self.prizey / self.by);
|
||||||
|
// There's probably a way to do this in a single pass, but :shrug:
|
||||||
|
if (self.bx * b < self.prizex or self.by * b < self.prizey) {
|
||||||
|
b += 1;
|
||||||
|
}
|
||||||
|
// This _shouldn't_ be necessary, but I don't trust myself with Zig arithmetic yet!
|
||||||
|
if (self.bx * b < self.prizex or self.by * b < self.prizey) {
|
||||||
|
print("Arithmetic error while operating on {}\n", .{self});
|
||||||
|
return CaseError.ArithmeticError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.bx * b == self.prizex and self.by * b == self.prizey and b <= 100) {
|
||||||
|
return .{ .a = 0, .b = b };
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// Could _probably do this as part of the `while` loop definition, but it makes more sense to my brain
|
||||||
|
// as the first statement like this.
|
||||||
|
a += 1;
|
||||||
|
while (self.ax * a + self.bx * b > self.prizex or self.ay * a + self.by * b > self.prizey) : (b -= 1) {
|
||||||
|
if (b == 0) {
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.ax * a + self.bx * b == self.prizex and self.ay * a + self.by * b == self.prizey and b <= 100) {
|
||||||
|
return .{ .a = a, .b = b };
|
||||||
|
} else {
|
||||||
|
while (self.ax * a + self.bx * b < self.prizex and self.ay * a + self.by * b < self.prizey) : (b += 1) {}
|
||||||
|
if (a == 100) {
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
} // else - continue loop, increment a, keep trying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CaseTakeTwo = struct {
|
||||||
|
ax: u64,
|
||||||
|
ay: u64,
|
||||||
|
bx: u64,
|
||||||
|
by: u64,
|
||||||
|
prizex: u64,
|
||||||
|
prizey: u64,
|
||||||
|
pub fn from_lines(a_line: []const u8, b_line: []const u8, prize_line: []const u8) anyerror!CaseTakeTwo {
|
||||||
|
// Looks like there are no regexes in Zig :shrug:
|
||||||
|
print("DEBUG - parsing a_line: {s}\n", .{a_line});
|
||||||
|
const start_of_ax: usize = 12;
|
||||||
|
const end_of_ax: usize = std.mem.indexOf(u8, a_line, ",").?;
|
||||||
|
const start_of_ay: usize = std.mem.indexOf(u8, a_line, "Y").? + 2;
|
||||||
|
const ax = try std.fmt.parseInt(u64, a_line[start_of_ax..end_of_ax], 10);
|
||||||
|
const ay = try std.fmt.parseInt(u64, a_line[start_of_ay..a_line.len], 10);
|
||||||
|
|
||||||
|
const start_of_bx: usize = 12;
|
||||||
|
const end_of_bx: usize = std.mem.indexOf(u8, b_line, ",").?;
|
||||||
|
const start_of_by: usize = std.mem.indexOf(u8, b_line, "Y").? + 2;
|
||||||
|
const bx = try std.fmt.parseInt(u64, b_line[start_of_bx..end_of_bx], 10);
|
||||||
|
const by = try std.fmt.parseInt(u64, b_line[start_of_by..b_line.len], 10);
|
||||||
|
|
||||||
|
const start_of_prizex: usize = 9;
|
||||||
|
const end_of_prizex: usize = std.mem.indexOf(u8, prize_line, ",").?;
|
||||||
|
const start_of_prizey: usize = std.mem.indexOf(u8, prize_line, "Y").? + 2;
|
||||||
|
const prizex = try std.fmt.parseInt(u64, prize_line[start_of_prizex..end_of_prizex], 10) + 10000000000000;
|
||||||
|
const prizey = try std.fmt.parseInt(u64, prize_line[start_of_prizey..prize_line.len], 10) + 10000000000000;
|
||||||
|
|
||||||
|
return CaseTakeTwo{ .ax = ax, .ay = ay, .bx = bx, .by = by, .prizex = prizex, .prizey = prizey };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve(self: *const CaseTakeTwo) CaseError!SolutionTakeTwo {
|
||||||
|
// The iterative approach doesn't work here :P take the exact numerical approach.
|
||||||
|
// A little algebraic rearrangement gives us:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// b ( x_1 y_2 - x_2 y_1) = x_1 t_y - y_1 t_x
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// So, if the LHS factor does not divide the RHS factor, we know the case is insoluble;
|
||||||
|
// and if they _do_ divide, this gives us an equation for b.
|
||||||
|
// (Question - there could be cases where there are multiple solutions, what would that look like? Cross that
|
||||||
|
// bridge when we come to it.)
|
||||||
|
|
||||||
|
// Because heaven forbid we have a filthy <gasp> *NEGATIVE NUMBER* in our data.
|
||||||
|
// Won't somebody please think of the children!
|
||||||
|
if ((self.ax * self.by) >= (self.bx * self.ay)) {
|
||||||
|
const lhs = (self.ax * self.by) - (self.bx * self.ay);
|
||||||
|
if (lhs == 0) {
|
||||||
|
print("SOMETHING WEIRD HAPPENED - zero-division in case {}\n", .{self});
|
||||||
|
return CaseError.ZeroDivisionError;
|
||||||
|
}
|
||||||
|
if (self.ax * self.prizey <= self.ay * self.prizex) {
|
||||||
|
// i.e. lhs and rhs would have different signs
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
}
|
||||||
|
const rhs = (self.ax * self.prizey - self.ay * self.prizex);
|
||||||
|
if (rhs % lhs != 0) {
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
}
|
||||||
|
// We know that lhs divides rhs exactly, so...
|
||||||
|
const b = @divExact(rhs, lhs);
|
||||||
|
print("DEBUG - b is {}\n", .{b});
|
||||||
|
|
||||||
|
const a_calc_numerator = self.prizex - (b * self.bx);
|
||||||
|
const a_calc_denominator = self.ax;
|
||||||
|
if (a_calc_numerator % a_calc_denominator != 0) {
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
}
|
||||||
|
const a = @divExact(a_calc_numerator, a_calc_denominator);
|
||||||
|
return SolutionTakeTwo{ .a = a, .b = b };
|
||||||
|
} else {
|
||||||
|
const lhs = (self.bx * self.ay) - (self.ax * self.by);
|
||||||
|
if (lhs == 0) {
|
||||||
|
print("SOMETHING WEIRD HAPPENED - zero-division in case {}\n", .{self});
|
||||||
|
return CaseError.ZeroDivisionError;
|
||||||
|
}
|
||||||
|
if (self.ay * self.prizex <= self.ax * self.prizey) {
|
||||||
|
// i.e. lhs and rhs would have different signs
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
}
|
||||||
|
const rhs = (self.ay * self.prizex - self.ax * self.prizey);
|
||||||
|
if (rhs % lhs != 0) {
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
}
|
||||||
|
// We know that lhs divides rhs exactly, so...
|
||||||
|
const b = @divExact(rhs, lhs);
|
||||||
|
print("DEBUG - b is {}\n", .{b});
|
||||||
|
|
||||||
|
const a_calc_numerator = self.prizex - (b * self.bx);
|
||||||
|
const a_calc_denominator = self.ax;
|
||||||
|
if (a_calc_numerator % a_calc_denominator != 0) {
|
||||||
|
return CaseError.InsolubleError;
|
||||||
|
}
|
||||||
|
const a = @divExact(a_calc_numerator, a_calc_denominator);
|
||||||
|
return SolutionTakeTwo{ .a = a, .b = b };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn part_one(is_test_case: bool) anyerror!u128 {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const input_file = try util.getInputFile("13", is_test_case);
|
||||||
|
const data = try util.readAllInputWithAllocator(input_file, allocator);
|
||||||
|
defer allocator.free(data);
|
||||||
|
|
||||||
|
var it = std.mem.splitScalar(u8, data, '\n');
|
||||||
|
var cases = std.ArrayList(Case).init(allocator);
|
||||||
|
defer cases.deinit();
|
||||||
|
while (true) {
|
||||||
|
const a_line = it.next().?;
|
||||||
|
const b_line = it.next().?;
|
||||||
|
const prize_line = it.next().?;
|
||||||
|
_ = it.next();
|
||||||
|
|
||||||
|
try cases.append(try Case.from_lines(a_line, b_line, prize_line));
|
||||||
|
|
||||||
|
if (it.peek() == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var total: u32 = 0;
|
||||||
|
for (cases.items) |case| {
|
||||||
|
print("Solution to case {}", .{case});
|
||||||
|
if (case.solve()) |solution| {
|
||||||
|
print(" is {}\n", .{solution});
|
||||||
|
total += solution.a * 3 + solution.b;
|
||||||
|
} else |_| {
|
||||||
|
print(" is impossible\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_two(is_test_case: bool) anyerror!u128 {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const input_file = try util.getInputFile("13", is_test_case);
|
||||||
|
const data = try util.readAllInputWithAllocator(input_file, allocator);
|
||||||
|
defer allocator.free(data);
|
||||||
|
|
||||||
|
var it = std.mem.splitScalar(u8, data, '\n');
|
||||||
|
var cases = std.ArrayList(CaseTakeTwo).init(allocator);
|
||||||
|
defer cases.deinit();
|
||||||
|
while (true) {
|
||||||
|
const a_line = it.next().?;
|
||||||
|
const b_line = it.next().?;
|
||||||
|
const prize_line = it.next().?;
|
||||||
|
_ = it.next();
|
||||||
|
|
||||||
|
try cases.append(try CaseTakeTwo.from_lines(a_line, b_line, prize_line));
|
||||||
|
|
||||||
|
if (it.peek() == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var total: u64 = 0;
|
||||||
|
for (cases.items) |case| {
|
||||||
|
print("Solution to case {}", .{case});
|
||||||
|
if (case.solve()) |solution| {
|
||||||
|
print(" is {}\n", .{solution});
|
||||||
|
total += solution.a * 3 + solution.b;
|
||||||
|
} else |_| {
|
||||||
|
print(" is impossible\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "part_one" {
|
||||||
|
const part_one_response = try part_one(true);
|
||||||
|
print("DEBUG - part_one_response is {}\n", .{part_one_response});
|
||||||
|
try expect(part_one_response == 480);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user