diff options
Diffstat (limited to 'aoc24')
-rw-r--r-- | aoc24/.gitignore | 1 | ||||
-rw-r--r-- | aoc24/build.zig | 111 | ||||
-rw-r--r-- | aoc24/src/day1.zig | 70 | ||||
-rw-r--r-- | aoc24/src/day1p1.zig | 49 | ||||
-rw-r--r-- | aoc24/src/day1p2.zig | 51 | ||||
-rw-r--r-- | aoc24/src/day2.zig (renamed from aoc24/src/day2p2.zig) | 68 | ||||
-rw-r--r-- | aoc24/src/day2p1.zig | 54 | ||||
-rw-r--r-- | aoc24/tools/download_input.zig | 39 | ||||
-rw-r--r-- | aoc24/tools/generate_main.zig | 37 |
9 files changed, 274 insertions, 206 deletions
diff --git a/aoc24/.gitignore b/aoc24/.gitignore index 21d9cef..2dd55ef 100644 --- a/aoc24/.gitignore +++ b/aoc24/.gitignore @@ -1,3 +1,4 @@ .zig-cache/ zig-out/ src/*.txt +.env diff --git a/aoc24/build.zig b/aoc24/build.zig index 4648193..7648bf0 100644 --- a/aoc24/build.zig +++ b/aoc24/build.zig @@ -15,50 +15,83 @@ pub fn build(b: *std.Build) void { // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); - for (1..26) |day| for ([_]usize{ 1, 2 }) |part| { - const name = b.fmt("day{}p{}", .{ day, part }); - const path = b.path(b.fmt("src/{s}.zig", .{name})); - const exe = b.addExecutable(.{ - .name = name, - .root_source_file = path, - .target = target, - .optimize = optimize, - }); + const generate_main = b.addExecutable(.{ + .name = "generate_main", + .root_source_file = b.path("tools/generate_main.zig"), + .target = b.host, + }); - // This declares intent for the executable to be installed into the - // standard location when the user invokes the "install" step (the default - // step when running `zig build`). - b.installArtifact(exe); + const download_input = b.addExecutable(.{ + .name = "download_input", + .root_source_file = b.path("tools/download_input.zig"), + .target = b.host, + }); - // This *creates* a Run step in the build graph, to be executed when another - // step is evaluated that depends on it. The next line below will establish - // such a dependency. - const run_cmd = b.addRunArtifact(exe); + for (1..26) |day| { + const run_download_input = b.addRunArtifact(download_input); + run_download_input.addArg(b.fmt("{}", .{day})); + const input_path = run_download_input.addOutputFileArg(b.fmt("{}.txt", .{day})); + const path = b.path(b.fmt("src/day{}.zig", .{day})); - // This allows the user to pass arguments to the application in the build - // command itself, like this: `zig build run -- arg1 arg2 etc` - if (b.args) |args| { - run_cmd.addArgs(args); - } + for ([_]usize{ 1, 2 }) |part| { + const name = b.fmt("day{}p{}", .{ day, part }); + + const run_generate_main = b.addRunArtifact(generate_main); + run_generate_main.addArg(b.fmt("{}", .{part})); + const main_path = run_generate_main.addOutputFileArg(b.fmt("{s}_main.zig", .{name})); + + const exe = b.addExecutable(.{ + .name = b.fmt("{s}", .{name}), + .root_source_file = main_path, + .target = target, + .optimize = optimize, + }); + exe.root_module.addAnonymousImport("solution", .{ + .root_source_file = path, + .target = target, + .optimize = optimize, + }); + exe.root_module.addAnonymousImport("input", .{ + .root_source_file = input_path, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); - // This creates a build step. It will be visible in the `zig build --help` menu, - // and can be selected like this: `zig build run` - // This will evaluate the `run` step rather than the default, which is "install". - const run_step = b.step(name, b.fmt("Run {s}", .{name})); - run_step.dependOn(&run_cmd.step); + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); - const exe_unit_tests = b.addTest(.{ - .root_source_file = path, - .target = target, - .optimize = optimize, - }); + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step(name, b.fmt("Run {s}", .{name})); + run_step.dependOn(&run_cmd.step); - // Similar to creating the run step earlier, this exposes a `test` step to - // the `zig build --help` menu, providing a way for the user to request - // running the unit tests. - const test_step = b.step(b.fmt("{s}-test", .{name}), b.fmt("Test {s}", .{name})); - test_step.dependOn(&run_exe_unit_tests.step); - }; + const exe_unit_tests = b.addTest(.{ + .root_source_file = path, + .target = target, + .optimize = optimize, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step(b.fmt("{s}-test", .{name}), b.fmt("Test {s}", .{name})); + test_step.dependOn(&run_exe_unit_tests.step); + } + } } diff --git a/aoc24/src/day1.zig b/aoc24/src/day1.zig new file mode 100644 index 0000000..1f26943 --- /dev/null +++ b/aoc24/src/day1.zig @@ -0,0 +1,70 @@ +const std = @import("std"); + +test { + std.debug.assert(try part1( + \\3 4 + \\4 3 + \\2 5 + \\1 3 + \\3 9 + \\3 3 + ) == 11); +} + +const Input = struct { + lefts: std.ArrayList(u32), + rights: std.ArrayList(u32), +}; + +pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Input { + var lines = std.mem.split(u8, input, "\n"); + + var lefts = std.ArrayList(u32).init(allocator); + // defer lefts.deinit(); + var rights = std.ArrayList(u32).init(allocator); + // defer rights.deinit(); + + while (lines.next()) |line| { + if (line.len == 0) continue; + var words = std.mem.split(u8, line, " "); + const left = try std.fmt.parseInt(u32, words.next() orelse return error.InvalidInput, 10); + const right = try std.fmt.parseInt(u32, words.next() orelse return error.InvalidInput, 10); + try lefts.append(left); + try rights.append(right); + } + + return .{ .lefts = lefts, .rights = rights }; +} + +pub fn part1(_: std.mem.Allocator, input: Input) !u32 { + std.mem.sort(u32, input.lefts.items, {}, std.sort.asc(u32)); + std.mem.sort(u32, input.rights.items, {}, std.sort.asc(u32)); + + var sum: u32 = 0; + for (input.lefts.items, input.rights.items) |l, r| { + sum += @max(l, r) - @min(l, r); + } + + return sum; +} + +pub fn part2(allocator: std.mem.Allocator, input: Input) !u32 { + var right_counts = std.AutoHashMap(u32, u16).init(allocator); + defer right_counts.deinit(); + + for (input.rights.items) |right| { + const entry = try right_counts.getOrPut(right); + if (entry.found_existing) + entry.value_ptr.* += 1 + else + entry.value_ptr.* = 1; + } + + var sum: u32 = 0; + for (input.lefts.items) |x| { + const count: u32 = @intCast(right_counts.get(x) orelse 0); + sum += x * count; + } + + return sum; +} diff --git a/aoc24/src/day1p1.zig b/aoc24/src/day1p1.zig deleted file mode 100644 index fba4b8c..0000000 --- a/aoc24/src/day1p1.zig +++ /dev/null @@ -1,49 +0,0 @@ -const std = @import("std"); - -pub fn main() !void { - const input = @embedFile("1.txt"); - try std.fmt.format(std.io.getStdOut().writer(), "{}\n", .{try run(input)}); -} - -test { - std.debug.assert(try run( - \\3 4 - \\4 3 - \\2 5 - \\1 3 - \\3 9 - \\3 3 - ) == 11); -} - -pub fn run(input: []const u8) !u32 { - var lines = std.mem.split(u8, input, "\n"); - - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - var lefts = std.ArrayList(u32).init(allocator); - defer lefts.deinit(); - var rights = std.ArrayList(u32).init(allocator); - defer rights.deinit(); - - while (lines.next()) |line| { - if (line.len == 0) continue; - var words = std.mem.split(u8, line, " "); - const left = try std.fmt.parseInt(u32, words.next() orelse return error.InvalidInput, 10); - const right = try std.fmt.parseInt(u32, words.next() orelse return error.InvalidInput, 10); - try lefts.append(left); - try rights.append(right); - } - - std.mem.sort(u32, lefts.items, {}, std.sort.asc(u32)); - std.mem.sort(u32, rights.items, {}, std.sort.asc(u32)); - - var sum: u32 = 0; - for (lefts.items, rights.items) |l, r| { - sum += @max(l, r) - @min(l, r); - } - - return sum; -} diff --git a/aoc24/src/day1p2.zig b/aoc24/src/day1p2.zig deleted file mode 100644 index 91b99fa..0000000 --- a/aoc24/src/day1p2.zig +++ /dev/null @@ -1,51 +0,0 @@ -const std = @import("std"); - -pub fn main() !void { - const input = @embedFile("1.txt"); - try std.fmt.format(std.io.getStdOut().writer(), "{}\n", .{try run(input)}); -} - -test { - std.debug.assert(try run( - \\3 4 - \\4 3 - \\2 5 - \\1 3 - \\3 9 - \\3 3 - ) == 31); -} - -pub fn run(input: []const u8) !u32 { - var lines = std.mem.split(u8, input, "\n"); - - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - var lefts = std.ArrayList(u32).init(allocator); - defer lefts.deinit(); - var rights = std.AutoHashMap(u32, u16).init(allocator); - defer rights.deinit(); - - while (lines.next()) |line| { - if (line.len == 0) continue; - var words = std.mem.split(u8, line, " "); - const left = try std.fmt.parseInt(u32, words.next() orelse return error.InvalidInput, 10); - const right = try std.fmt.parseInt(u32, words.next() orelse return error.InvalidInput, 10); - try lefts.append(left); - const entry = try rights.getOrPut(right); - if (entry.found_existing) - entry.value_ptr.* += 1 - else - entry.value_ptr.* = 1; - } - - var sum: u32 = 0; - for (lefts.items) |x| { - const count: u32 = @intCast(rights.get(x) orelse 0); - sum += x * count; - } - - return sum; -} diff --git a/aoc24/src/day2p2.zig b/aoc24/src/day2.zig index fd2f7a3..656fd41 100644 --- a/aoc24/src/day2p2.zig +++ b/aoc24/src/day2.zig @@ -1,12 +1,28 @@ const std = @import("std"); -pub fn main() !void { - const input = @embedFile("2.txt"); - try std.fmt.format(std.io.getStdOut().writer(), "{}\n", .{try run(input)}); +test "part 1" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + const input = try parse(allocator, + \\7 6 4 2 1 + \\1 2 7 8 9 + \\9 7 6 2 1 + \\1 3 2 4 5 + \\8 6 4 4 1 + \\1 3 6 7 9 + ); + const value = try part1(allocator, input); + const ok = value == 2; + if (!ok) std.debug.print("got {}\n", .{value}); + std.debug.assert(ok); } -test { - const value = try run( +test "part 2" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + const input = try parse(allocator, \\7 6 4 2 1 \\1 2 7 8 9 \\9 7 6 2 1 @@ -14,19 +30,19 @@ test { \\8 6 4 4 1 \\1 3 6 7 9 ); - std.debug.print("got {}\n", .{value}); - std.debug.assert(value == 4); + const value = try part2(allocator, input); + const ok = value == 4; + if (!ok) std.debug.print("got {}\n", .{value}); + std.debug.assert(ok); } -pub fn run(input: []const u8) !u32 { +pub fn parse( + allocator: std.mem.Allocator, + input: []const u8, +) !std.ArrayList(std.ArrayList(u32)) { var lines = std.mem.split(u8, input, "\n"); - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - var reports = std.ArrayList(std.ArrayList(u32)).init(allocator); - defer reports.deinit(); while (lines.next()) |line| { if (line.len == 0) continue; @@ -38,6 +54,32 @@ pub fn run(input: []const u8) !u32 { try reports.append(report); } + return reports; +} + +pub fn part1( + _: std.mem.Allocator, + reports: std.ArrayList(std.ArrayList(u32)), +) !u32 { + var safe: u32 = 0; + for (reports.items) |report| { + const increasing = report.items[0] < report.items[1]; + for (report.items[0 .. report.items.len - 1], report.items[1..]) |prev, curr| { + if ((prev < curr) != increasing) break; + if (prev == curr) break; + if (@max(prev, curr) - @min(prev, curr) > 3) break; + } else { + safe += 1; + } + } + + return safe; +} + +pub fn part2( + _: std.mem.Allocator, + reports: std.ArrayList(std.ArrayList(u32)), +) !u32 { var safe: u32 = 0; for (reports.items) |report| { var is_safe = false; diff --git a/aoc24/src/day2p1.zig b/aoc24/src/day2p1.zig deleted file mode 100644 index f5263be..0000000 --- a/aoc24/src/day2p1.zig +++ /dev/null @@ -1,54 +0,0 @@ -const std = @import("std"); - -pub fn main() !void { - const input = @embedFile("2.txt"); - try std.fmt.format(std.io.getStdOut().writer(), "{}\n", .{try run(input)}); -} - -test { - const value = try run( - \\7 6 4 2 1 - \\1 2 7 8 9 - \\9 7 6 2 1 - \\1 3 2 4 5 - \\8 6 4 4 1 - \\1 3 6 7 9 - ); - std.debug.print("got {}\n", .{value}); - std.debug.assert(value == 2); -} - -pub fn run(input: []const u8) !u32 { - var lines = std.mem.split(u8, input, "\n"); - - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - var reports = std.ArrayList(std.ArrayList(u32)).init(allocator); - defer reports.deinit(); - - while (lines.next()) |line| { - if (line.len == 0) continue; - var report = std.ArrayList(u32).init(allocator); - var words = std.mem.split(u8, line, " "); - while (words.next()) |word| { - try report.append(try std.fmt.parseInt(u32, word, 10)); - } - try reports.append(report); - } - - var safe: u32 = 0; - for (reports.items) |report| { - const increasing = report.items[0] < report.items[1]; - for (report.items[0 .. report.items.len - 1], report.items[1..]) |prev, curr| { - if ((prev < curr) != increasing) break; - if (prev == curr) break; - if (@max(prev, curr) - @min(prev, curr) > 3) break; - } else { - safe += 1; - } - } - - return safe; -} diff --git a/aoc24/tools/download_input.zig b/aoc24/tools/download_input.zig new file mode 100644 index 0000000..87c229a --- /dev/null +++ b/aoc24/tools/download_input.zig @@ -0,0 +1,39 @@ +const std = @import("std"); + +pub fn main() !void { + std.debug.print("Downloading input from adventofcode.com\n", .{}); + + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + const args = try std.process.argsAlloc(allocator); + + if (args.len != 3) return error.InvalidArguments; + + const day = try std.fmt.parseInt(u32, args[1], 10); + const output_file_path = args[2]; + + var output_file = try std.fs.cwd().createFile(output_file_path, .{}); + defer output_file.close(); + + const url = try std.fmt.allocPrint(allocator, "https://adventofcode.com/2024/day/{}/input", .{day}); + defer allocator.free(url); + const session_cookie_header = try std.fmt.allocPrint(allocator, "session={s}", .{ + std.posix.getenv("AOC_SESSION") orelse return error.MissingAocSession, + }); + defer allocator.free(session_cookie_header); + var response = std.ArrayList(u8).init(allocator); + var client = std.http.Client{ .allocator = allocator }; + const result = try client.fetch(.{ + .location = .{ .url = url }, + .response_storage = .{ .dynamic = &response }, + .extra_headers = &[_]std.http.Header{ + .{ .name = "Cookie", .value = session_cookie_header }, + }, + }); + if (result.status != .ok) return error.InvalidStatusCode; + + try output_file.writeAll(response.items); + return std.process.cleanExit(); +} diff --git a/aoc24/tools/generate_main.zig b/aoc24/tools/generate_main.zig new file mode 100644 index 0000000..e149127 --- /dev/null +++ b/aoc24/tools/generate_main.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + const args = try std.process.argsAlloc(allocator); + + if (args.len != 3) return error.InvalidArguments; + + const part = try std.fmt.parseInt(u32, args[1], 10); + const output_file_path = args[2]; + + var output_file = try std.fs.cwd().createFile(output_file_path, .{}); + defer output_file.close(); + + try output_file.writer().print( + \\const std = @import("std"); + \\const solution = @import("solution"); + \\ + \\pub fn main() !void {{ + \\ const data = @embedFile("input"); + \\ + \\ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + \\ defer arena.deinit(); + \\ const allocator = arena.allocator(); + \\ + \\ const input = try solution.parse(allocator, data); + \\ const output = try solution.part{}(allocator, input); + \\ try std.fmt.format(std.io.getStdOut().writer(), "{{}}\n", .{{output}}); + \\}} + , + .{part}, + ); + return std.process.cleanExit(); +} |