2024 Day 21 Part 1
This commit is contained in:
parent
0b2bf24c9e
commit
4015e46237
1 changed files with 462 additions and 0 deletions
462
2024/src/day21.zig
Normal file
462
2024/src/day21.zig
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
const std = @import("std");
|
||||
const aoc = @import("aoc.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const List = std.ArrayList;
|
||||
const Map = std.AutoHashMap;
|
||||
const Set = aoc.AutoHashSet;
|
||||
const PriorityQueue = std.PriorityQueue;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const text = try aoc.readAll(allocator, "input/day21.txt");
|
||||
defer allocator.free(text);
|
||||
|
||||
const codes = try aoc.splitAll(allocator, text, "\n");
|
||||
defer codes.deinit();
|
||||
|
||||
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
|
||||
const stdout = bw.writer();
|
||||
|
||||
try stdout.print("{}\n", .{try part1(allocator, codes.items)});
|
||||
try bw.flush();
|
||||
}
|
||||
|
||||
const String = []const u8;
|
||||
|
||||
fn part1(allocator: Allocator, codes: []String) !u64 {
|
||||
var sum: u64 = 0;
|
||||
for (codes) |code| {
|
||||
const presses = try solve(allocator, code);
|
||||
|
||||
sum += complexity(code, @intCast(presses));
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
fn complexity(code: String, presses: u64) u64 {
|
||||
const a, const n = takeInt(code).?;
|
||||
if (!std.mem.eql(u8, a, "A")) unreachable;
|
||||
|
||||
return n * presses;
|
||||
}
|
||||
|
||||
fn takeInt(input: []const u8) ?struct { []const u8, u64 } {
|
||||
var i: usize = 0;
|
||||
var n: u64 = 0;
|
||||
|
||||
while (i < input.len and std.ascii.isDigit(input[i])) : (i += 1) {
|
||||
n *= 10;
|
||||
n += input[i] - '0';
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
// Nothing consumed
|
||||
return null;
|
||||
}
|
||||
|
||||
return .{ input[i..], n };
|
||||
}
|
||||
|
||||
fn solve(allocator: Allocator, code: String) !usize {
|
||||
// Step 0: The actual passcode (numpad)
|
||||
var digits = List(Numpad).init(allocator);
|
||||
defer digits.deinit();
|
||||
for (code) |c| try digits.append(Numpad.parse(c));
|
||||
|
||||
var total: usize = 0;
|
||||
var start = Numpad.a;
|
||||
for (digits.items) |next| {
|
||||
total += try digitCost(allocator, start, next);
|
||||
start = next;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
fn digitCost(allocator: Allocator, start: Numpad, end: Numpad) !usize {
|
||||
var best: usize = std.math.maxInt(usize);
|
||||
const digits = [_]Numpad{end};
|
||||
|
||||
// Step 1: Depressurized (dpad)
|
||||
var robot1 = try numpadToDpad(allocator, digits[0..], start);
|
||||
defer robot1.deinit();
|
||||
while (try robot1.next()) |r1| {
|
||||
defer r1.deinit();
|
||||
|
||||
// Step 2: High radiation (dpad)
|
||||
var robot2 = try dpadToDpad(allocator, r1.items, Dpad.a);
|
||||
defer robot2.deinit();
|
||||
while (try robot2.next()) |r2| {
|
||||
defer r2.deinit();
|
||||
|
||||
// Step 3: Very cold (dpad)
|
||||
var robot3 = try dpadToDpad(allocator, r2.items, Dpad.a);
|
||||
defer robot3.deinit();
|
||||
while (try robot3.next()) |r3| {
|
||||
defer r3.deinit();
|
||||
|
||||
// Step 4: Direct manipulation (no cost!)
|
||||
if (r3.items.len < best) {
|
||||
best = r3.items.len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
fn numpadToDpad(allocator: Allocator, output: []const Numpad, start: Numpad) !ButtonIterator(Numpad, Dpad) {
|
||||
return try ButtonIterator(Numpad, Dpad).init(allocator, output, start);
|
||||
}
|
||||
|
||||
fn dpadToDpad(allocator: Allocator, output: []Dpad, start: Dpad) !ButtonIterator(Dpad, Dpad) {
|
||||
return try ButtonIterator(Dpad, Dpad).init(allocator, output, start);
|
||||
}
|
||||
|
||||
fn ButtonIterator(comptime Target: type, comptime Input: type) type {
|
||||
return struct {
|
||||
allocator: Allocator,
|
||||
stack: List(State),
|
||||
|
||||
const Self = @This();
|
||||
const State = struct {
|
||||
prev: Target,
|
||||
buttons: []const Target,
|
||||
inputs: List(Input),
|
||||
|
||||
fn deinit(self: *State) void {
|
||||
self.inputs.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
fn init(allocator: Allocator, buttons: []const Target, start: Target) !Self {
|
||||
var stack = List(State).init(allocator);
|
||||
try stack.append(State{
|
||||
.prev = start,
|
||||
.buttons = buttons,
|
||||
.inputs = List(Input).init(allocator),
|
||||
});
|
||||
|
||||
return Self{
|
||||
.allocator = allocator,
|
||||
.stack = stack,
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
for (self.stack.items) |*s| s.deinit();
|
||||
self.stack.deinit();
|
||||
}
|
||||
|
||||
fn next(self: *Self) !?List(Input) {
|
||||
while (self.stack.popOrNull()) |state| {
|
||||
const buttons, var inputs = .{ state.buttons, state.inputs };
|
||||
|
||||
if (buttons.len == 0) {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
defer inputs.deinit();
|
||||
|
||||
const start = state.prev;
|
||||
const btn = buttons[0];
|
||||
const delta = btn.pos().sub(start.pos());
|
||||
|
||||
var moves = try arrows(self.allocator, delta);
|
||||
defer moves.deinit();
|
||||
while (try moves.next()) |inp| {
|
||||
defer inp.deinit();
|
||||
|
||||
var ii = try inputs.clone();
|
||||
// Move to button (arrows)
|
||||
var pos = start.pos();
|
||||
for (inp.items) |i| {
|
||||
pos = pos.add(i.delta());
|
||||
if (!Target.valid(pos)) {
|
||||
ii.deinit();
|
||||
break;
|
||||
}
|
||||
try ii.append(i);
|
||||
} else {
|
||||
// Press button (A)
|
||||
try ii.append(Input.a);
|
||||
|
||||
try self.stack.append(State{
|
||||
.prev = btn,
|
||||
.buttons = buttons[1..],
|
||||
.inputs = ii,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn arrows(allocator: Allocator, delta: Delta) !MovesIterator {
|
||||
return try MovesIterator.init(allocator, delta);
|
||||
}
|
||||
|
||||
const MovesIterator = struct {
|
||||
allocator: Allocator,
|
||||
stack: List(State),
|
||||
|
||||
const Self = @This();
|
||||
const State = struct {
|
||||
delta: Delta,
|
||||
moves: List(Dpad),
|
||||
|
||||
fn deinit(self: *State) void {
|
||||
self.moves.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
fn init(allocator: Allocator, delta: Delta) !Self {
|
||||
var stack = List(State).init(allocator);
|
||||
try stack.append(State{
|
||||
.delta = delta,
|
||||
.moves = List(Dpad).init(allocator),
|
||||
});
|
||||
|
||||
return Self{
|
||||
.allocator = allocator,
|
||||
.stack = stack,
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
for (self.stack.items) |*s| s.deinit();
|
||||
self.stack.deinit();
|
||||
}
|
||||
|
||||
fn next(self: *Self) !?List(Dpad) {
|
||||
while (self.stack.popOrNull()) |state| {
|
||||
const delta, var moves = .{ state.delta, state.moves };
|
||||
const dr, const dc = .{ delta.dr, delta.dc };
|
||||
|
||||
if (dr == 0 and dc == 0) {
|
||||
return moves;
|
||||
}
|
||||
|
||||
defer moves.deinit();
|
||||
|
||||
if (dr < 0) {
|
||||
var m = try moves.clone();
|
||||
try appendMulti(Dpad, &m, Dpad.up, @intCast(@abs(dr)));
|
||||
|
||||
try self.stack.append(State{
|
||||
.delta = Delta.new(0, dc),
|
||||
.moves = m,
|
||||
});
|
||||
}
|
||||
|
||||
if (dr > 0) {
|
||||
var m = try moves.clone();
|
||||
try appendMulti(Dpad, &m, Dpad.down, @intCast(@abs(dr)));
|
||||
|
||||
try self.stack.append(State{
|
||||
.delta = Delta.new(0, dc),
|
||||
.moves = m,
|
||||
});
|
||||
}
|
||||
|
||||
if (dc < 0) {
|
||||
var m = try moves.clone();
|
||||
try appendMulti(Dpad, &m, Dpad.left, @intCast(@abs(dc)));
|
||||
|
||||
try self.stack.append(State{
|
||||
.delta = Delta.new(dr, 0),
|
||||
.moves = m,
|
||||
});
|
||||
}
|
||||
|
||||
if (dc > 0) {
|
||||
var m = try moves.clone();
|
||||
try appendMulti(Dpad, &m, Dpad.right, @intCast(@abs(dc)));
|
||||
|
||||
try self.stack.append(State{
|
||||
.delta = Delta.new(dr, 0),
|
||||
.moves = m,
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
fn appendMulti(comptime T: type, l: *List(T), elt: T, n: usize) !void {
|
||||
for (0..n) |_| {
|
||||
try l.append(elt);
|
||||
}
|
||||
}
|
||||
|
||||
const Numpad = enum {
|
||||
one,
|
||||
two,
|
||||
three,
|
||||
four,
|
||||
five,
|
||||
six,
|
||||
seven,
|
||||
eight,
|
||||
nine,
|
||||
zero,
|
||||
a,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn pos(self: Self) Pos {
|
||||
return switch (self) {
|
||||
.seven => Pos.new(0, 0),
|
||||
.eight => Pos.new(0, 1),
|
||||
.nine => Pos.new(0, 2),
|
||||
.four => Pos.new(1, 0),
|
||||
.five => Pos.new(1, 1),
|
||||
.six => Pos.new(1, 2),
|
||||
.one => Pos.new(2, 0),
|
||||
.two => Pos.new(2, 1),
|
||||
.three => Pos.new(2, 2),
|
||||
// empty at (3, 0)
|
||||
.zero => Pos.new(3, 1),
|
||||
.a => Pos.new(3, 2),
|
||||
};
|
||||
}
|
||||
|
||||
fn valid(p: Pos) bool {
|
||||
return !(p.r == 3 and p.c == 0);
|
||||
}
|
||||
|
||||
fn parse(c: u8) Self {
|
||||
return switch (c) {
|
||||
'1' => .one,
|
||||
'2' => .two,
|
||||
'3' => .three,
|
||||
'4' => .four,
|
||||
'5' => .five,
|
||||
'6' => .six,
|
||||
'7' => .seven,
|
||||
'8' => .eight,
|
||||
'9' => .nine,
|
||||
'0' => .zero,
|
||||
'A' => .a,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
self: Numpad,
|
||||
comptime _: []const u8,
|
||||
_: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
const c: u8 = switch (self) {
|
||||
.one => '1',
|
||||
.two => '2',
|
||||
.three => '3',
|
||||
.four => '4',
|
||||
.five => '5',
|
||||
.six => '6',
|
||||
.seven => '7',
|
||||
.eight => '8',
|
||||
.nine => '9',
|
||||
.zero => '0',
|
||||
.a => 'A',
|
||||
};
|
||||
try writer.print("{c}", .{c});
|
||||
}
|
||||
};
|
||||
|
||||
const Dpad = enum {
|
||||
up,
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
a,
|
||||
|
||||
pub fn pos(self: Dpad) Pos {
|
||||
return switch (self) {
|
||||
// empty at (0, 0)
|
||||
.up => Pos.new(0, 1),
|
||||
.a => Pos.new(0, 2),
|
||||
.left => Pos.new(1, 0),
|
||||
.down => Pos.new(1, 1),
|
||||
.right => Pos.new(1, 2),
|
||||
};
|
||||
}
|
||||
|
||||
fn valid(p: Pos) bool {
|
||||
return !(p.r == 0 and p.c == 0);
|
||||
}
|
||||
|
||||
pub fn delta(self: Dpad) Delta {
|
||||
return switch (self) {
|
||||
.up => Delta.new(-1, 0),
|
||||
.down => Delta.new(1, 0),
|
||||
.left => Delta.new(0, -1),
|
||||
.right => Delta.new(0, 1),
|
||||
.a => Delta.new(0, 0),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
self: Dpad,
|
||||
comptime _: []const u8,
|
||||
_: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
const c: u8 = switch (self) {
|
||||
.up => '^',
|
||||
.down => 'v',
|
||||
.left => '<',
|
||||
.right => '>',
|
||||
.a => 'A',
|
||||
};
|
||||
try writer.print("{c}", .{c});
|
||||
}
|
||||
};
|
||||
|
||||
const Pos = struct {
|
||||
r: i32,
|
||||
c: i32,
|
||||
|
||||
fn new(r: i32, c: i32) Pos {
|
||||
return Pos{ .r = r, .c = c };
|
||||
}
|
||||
|
||||
fn add(self: Pos, d: Delta) Pos {
|
||||
return .{
|
||||
.r = self.r + d.dr,
|
||||
.c = self.c + d.dc,
|
||||
};
|
||||
}
|
||||
|
||||
fn sub(self: Pos, other: Pos) Delta {
|
||||
return .{
|
||||
.dr = self.r - other.r,
|
||||
.dc = self.c - other.c,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
self: Pos,
|
||||
comptime _: []const u8,
|
||||
_: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
try writer.print("({d}, {d})", .{ self.r, self.c });
|
||||
}
|
||||
};
|
||||
|
||||
const Delta = struct {
|
||||
dr: i32,
|
||||
dc: i32,
|
||||
|
||||
fn new(dr: i32, dc: i32) Delta {
|
||||
return Delta{ .dr = dr, .dc = dc };
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue