1
0

Compare commits

..

43 Commits

Author SHA1 Message Date
aeea7ee99c day10?
Some checks failed
Build and test / test (push) Failing after 2m16s
2024-07-30 13:43:06 +10:00
6065242de9 wip
Some checks failed
Build and test / test (push) Failing after 2m6s
2023-12-16 17:37:04 +11:00
a684d9b238 mild washing
Some checks failed
Build and test / test (push) Failing after 2m11s
2023-12-15 17:18:07 +11:00
992673f784 day15
Some checks failed
Build and test / test (push) Failing after 1m42s
2023-12-15 17:06:14 +11:00
0b6eda2892 wip part2
Some checks failed
Build and test / test (push) Failing after 1m46s
2023-12-14 17:44:03 +11:00
b9bff3998e day14 part1 2023-12-14 17:25:19 +11:00
b13d5bb5f9 light cleaning
Some checks failed
Build and test / test (push) Failing after 1m41s
2023-12-13 17:39:49 +11:00
06ce20853a day13 part2 2023-12-13 17:16:14 +11:00
794dddf854 day13 part1 2023-12-13 16:51:23 +11:00
fa7a9aab14 light washing, fix overflows
Some checks failed
Build and test / test (push) Failing after 1m43s
2023-12-13 13:52:57 +11:00
d80a39e7dc day12 part2
jesus
2023-12-13 13:41:34 +11:00
3e8dca665f day12 part1 2023-12-13 12:24:28 +11:00
30d638ca51 fix pipeline
Some checks failed
Build and test / test (push) Failing after 1m44s
2023-12-12 16:22:14 +11:00
db9172ac2a lmao 2023-12-12 16:20:34 +11:00
96a2a09620 day11 part2
Some checks failed
Build and test / test (push) Failing after 1m48s
2023-12-11 18:07:04 +11:00
33297cb653 day11 part1 2023-12-11 17:42:39 +11:00
b5583ca46e clean day09
Some checks failed
Build and test / test (push) Failing after 1m49s
2023-12-09 20:46:40 +11:00
9b8ae4f350 day09 part2
Some checks failed
Build and test / test (push) Failing after 1m43s
2023-12-09 20:45:13 +11:00
9035d0a8d4 day09 part1 2023-12-09 20:27:00 +11:00
3d36485a52 Solve day 3
Some checks failed
Build and test / test (push) Failing after 1m42s
2023-12-09 20:05:26 +11:00
84344d12e5 day08 part2
Some checks failed
Build and test / test (push) Failing after 1m53s
2023-12-08 17:07:55 +11:00
ca84923990 day08 part1 2023-12-08 16:46:26 +11:00
300ceb885f wash day07
Some checks failed
Build and test / test (push) Failing after 2m40s
2023-12-07 17:19:51 +11:00
221cad1136 unwashed day07 2023-12-07 17:10:52 +11:00
05ffa1e7c1 day07 part1 2023-12-07 17:05:56 +11:00
fbca1996a5 unwash day06 or something
Some checks failed
Build and test / test (push) Failing after 2m34s
2023-12-06 16:34:47 +11:00
d848f3951b wtf happened in day06 2023-12-06 16:31:50 +11:00
0ab1fa186d wash day05 2023-12-06 11:19:55 +11:00
0f2044b762 hey lmao day05 2023-12-06 11:17:05 +11:00
faf81869d6 wtf
Some checks failed
Build and test / test (push) Failing after 2m35s
2023-12-05 17:50:38 +11:00
d85d4f0d26 day05 part2 OOM killed lmao 2023-12-05 16:46:39 +11:00
052bf70095 Add template
Some checks failed
Build and test / test (push) Failing after 2m37s
2023-12-04 18:05:09 +11:00
9c64d710a3 day04: solve
Add a few util functions now that I'm getting sick of typing
std.mem.splitScalar.
2023-12-04 18:02:54 +11:00
e112876796 Fix GPA decl
Some checks failed
Build and test / test (push) Failing after 2m37s
2023-12-02 18:36:35 +11:00
362b2c2cef day02: solve 2023-12-02 18:36:30 +11:00
44ccf7b6e2 day01: merge silver/gold solutions
Some checks failed
Build and test / test (push) Failing after 2m53s
2023-12-02 00:24:51 +11:00
d5ee0f6aef day01: include both part 1 and 2 2023-12-02 00:11:58 +11:00
87d3e72276 day01: part2
Some checks failed
Build and test / test (push) Failing after 2m38s
2023-12-01 17:02:12 +11:00
ec97bd8ad7 day01: part1 2023-12-01 16:28:46 +11:00
cf4dea84d9 Add AOC CLI to flake
All checks were successful
Build and test / test (push) Successful in 2m57s
2023-11-30 14:52:15 +11:00
e70c44437e Add day-based build/test/run system 2023-11-30 14:52:04 +11:00
9286a82d86 Add LICENSE
All checks were successful
Build and test / test (push) Successful in 2m44s
2023-11-30 12:57:50 +11:00
52371f9e13 Remove build-zig-package
Turns out this actually is already implemented upstream.
2023-11-30 12:52:45 +11:00
26 changed files with 2189 additions and 134 deletions

View File

@ -12,5 +12,7 @@ jobs:
uses: https://github.com/cachix/install-nix-action@v23 uses: https://github.com/cachix/install-nix-action@v23
with: with:
github_access_token: ${{ secrets.INPUT_GITHUB_ACCESS_TOKEN }} github_access_token: ${{ secrets.INPUT_GITHUB_ACCESS_TOKEN }}
- name: Stub data files
run: touch src/data/day{01..31}.txt
- name: Build package - name: Build package
run: nix build run: nix build

3
.gitignore vendored
View File

@ -69,3 +69,6 @@ docgen_tmp/
### nix ### ### nix ###
result result
### local ###
src/data

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2023 xeals
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -15,56 +15,57 @@ pub fn build(b: *std.Build) void {
// set a preferred release mode, allowing the user to decide how to optimize. // set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
// Build harness based on https://github.com/SpexGuy/Zig-AoC-Template.
// Build, run, and install targets for "all."
const build_all = b.step("all", "Build all days");
const run_all = b.step("run", "Run all days");
const test_all = b.step("test", "Run all tests");
// Generate build, run, and install targets for all days that have been
// created.
for (1..25) |day| {
const day_string = b.fmt("day{:0>2}", .{day});
const src = b.fmt("src/{s}.zig", .{day_string});
std.fs.cwd().access(src, .{}) catch continue;
// Build and install
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "aoc23", .name = b.fmt("aoc23-{s}", .{day_string}),
// In this case the main source file is merely a path, however, in more .root_source_file = .{ .path = src },
// complicated build scripts, this could be a generated file.
.root_source_file = .{ .path = "src/main.zig" },
.target = target, .target = target,
.optimize = optimize, .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); b.installArtifact(exe);
build_all.dependOn(&exe.step);
// This *creates* a Run step in the build graph, to be executed when another // Run
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep()); run_cmd.step.dependOn(b.getInstallStep());
// 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| { if (b.args) |args| {
run_cmd.addArgs(args); run_cmd.addArgs(args);
} }
// This creates a build step. It will be visible in the `zig build --help` menu, const run_step = b.step(
// and can be selected like this: `zig build run` b.fmt("run-{s}", .{day_string}),
// This will evaluate the `run` step rather than the default, which is "install". b.fmt("Run {s} executable", .{day_string}),
const run_step = b.step("run", "Run the app"); );
run_step.dependOn(&run_cmd.step); run_step.dependOn(&run_cmd.step);
run_all.dependOn(&run_cmd.step);
// Creates a step for unit testing. This only builds the test executable // Test
// but does not run it. const unit_test = b.addTest(.{
const unit_tests = b.addTest(.{ .root_source_file = .{ .path = src },
.root_source_file = .{ .path = "src/main.zig" },
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
const run_unit_tests = b.addRunArtifact(unit_tests); const test_cmd = b.addRunArtifact(unit_test);
const test_step = b.step(
// Similar to creating the run step earlier, this exposes a `test` step to b.fmt("test-{s}", .{day_string}),
// the `zig build --help` menu, providing a way for the user to request b.fmt("Run {s} tests", .{day_string}),
// running the unit tests. );
const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&test_cmd.step);
test_step.dependOn(&run_unit_tests.step); test_all.dependOn(&test_cmd.step);
}
} }

View File

@ -11,19 +11,28 @@
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
zig = pkgs.zig_0_11; zig = pkgs.zig_0_11;
buildZigPackage = import ./nix/build-zig-package.nix {
inherit (pkgs) stdenv;
inherit zig;
};
in in
{ {
devShells.default = pkgs.mkShellNoCC { devShells.default = pkgs.mkShellNoCC {
buildInputs = [ zig pkgs.zls ]; buildInputs = [
pkgs.aoc-cli
zig
pkgs.zls
];
}; };
packages.default = buildZigPackage { packages.default = pkgs.stdenv.mkDerivation {
name = "aoc23"; name = "aoc23";
src = pkgs.nix-gitignore.gitignoreSource [ ] ./.; src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
nativeBuildInputs = [ zig.hook ];
meta = {
description = "Advent of Code 2023";
homepage = "https://git.xeal.me/xeals/aoc23";
license = nixpkgs.lib.licenses.mit;
inherit (zig.meta) platforms;
};
}; };
}); });
} }

View File

@ -1,57 +0,0 @@
{ stdenv
, zig
}:
{ buildInputs ? [ ]
, nativeBuildInputs ? [ ]
, optimize ? "ReleaseSafe"
, meta ? { }
, ...
}@args:
stdenv.mkDerivation (args // {
nativeBuildInputs = nativeBuildInputs ++ [
zig
];
buildInputs = buildInputs;
strictDeps = true;
configurePhase = args.configurePhase or ''
runHook preConfigure
runHook postConfigure
'';
ZIGFLAGS = args.ZIGFLAGS or [
"-Doptimize=${optimize}"
];
# https://github.com/ziglang/zig/issues/6810 requires setting XDG_CACHE_HOME
# in all zig build phases. --cache-dir and --global-cache-dir do not prevent
# the builder from attempting to create the XDG cache.
buildPhase = args.buildPhase or ''
runHook preBuild
XDG_CACHE_HOME=_cache zig build $ZIGFLAGS
runHook postBuild
'';
doCheck = args.doCheck or true;
checkPhase = args.checkPhase or ''
runHook preCheck
XDG_CACHE_HOME=_cache zig build test $ZIGFLAGS
runHook postCheck
'';
installPhase = args.installPhase or ''
runHook preInstall
XDG_CACHE_HOME=_cache zig build install --prefix $out $ZIGFLAGS
runHook postInstall
'';
meta = {
# default to Zig's platforms
platforms = zig.meta.platforms;
} // meta;
})

0
src/data/.gitkeep Normal file
View File

74
src/day01.zig Normal file
View File

@ -0,0 +1,74 @@
const std = @import("std");
const util = @import("util.zig");
const numbers = [_][]const u8{ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
pub fn main() !void {
const input = @embedFile("data/day01.txt");
std.debug.print("{d}\n", .{try solve(input, &[_][]u8{})});
std.debug.print("{d}\n", .{try solve(input, &numbers)});
}
const zero = @as(u8, '0');
fn solve(input: []const u8, words: []const []const u8) !u32 {
var sum: u32 = 0;
var it = std.mem.splitAny(u8, input, "\n");
while (it.next()) |line| {
if (std.mem.eql(u8, "", line)) {
continue;
}
var first: u8 = 0;
var last: u8 = 0;
for (0..line.len) |offset| {
var n: u8 = 0; // ascii value
if (line[offset] >= '0' and line[offset] <= '9') {
n = line[offset];
} else {
for (words, 0..) |word, value| {
if (std.mem.startsWith(u8, line[offset..], word)) {
n = @as(u8, @intCast(value)) + zero;
break;
}
}
}
if (n != 0) {
if (first == 0) {
first = n;
}
last = n;
}
}
const cal_value = (first - zero) * 10 + (last - zero);
sum += cal_value;
}
return sum;
}
test "silver" {
const input =
\\1abc2
\\pqr3stu8vwx
\\a1b2c3d4e5f
\\treb7uchet
;
try std.testing.expectEqual(@as(u32, 142), try solve(input, &[_][]u8{}));
}
test "gold" {
const input =
\\two1nine
\\eightwothree
\\abcone2threexyz
\\xtwone3four
\\4nineeightseven2
\\zoneight234
\\7pqrstsixteen
;
try std.testing.expectEqual(@as(u32, 281), try solve(input, &numbers));
}

94
src/day02.zig Normal file
View File

@ -0,0 +1,94 @@
const std = @import("std");
pub fn main() !void {
const input = @embedFile("data/day02.txt");
const target = Game{ .red = 12, .green = 13, .blue = 14 };
const sln = try solve(input, target);
std.debug.print("{d}\n", .{sln.possible});
std.debug.print("{d}\n", .{sln.powers});
}
const Day02Error = error{
MissingColon,
};
const Game = struct {
red: u32 = 0,
blue: u32 = 0,
green: u32 = 0,
fn parse(line: []const u8) !@This() {
var self = @This(){};
var n: u32 = 0;
const colon = std.mem.indexOfScalar(u8, line, ':') orelse return Day02Error.MissingColon;
var it = std.mem.splitAny(u8, line[colon + 1 ..], ",; ");
while (it.next()) |item| {
if (item.len == 0) {
continue;
}
switch (item[0]) {
'r' => self.red = @max(self.red, n),
'g' => self.green = @max(self.green, n),
'b' => self.blue = @max(self.blue, n),
'0'...'9' => n = try std.fmt.parseInt(u8, item, 10),
else => unreachable,
}
}
return self;
}
fn possible(self: @This(), other: @This()) bool {
return self.red <= other.red and self.green <= other.green and self.blue <= other.blue;
}
};
const Solution = struct {
possible: u32,
powers: u32,
};
fn solve(input: []const u8, target: Game) !Solution {
var possible: u32 = 0;
var powers: u32 = 0;
var index: u32 = 1;
var line_it = std.mem.splitScalar(u8, input, '\n');
while (line_it.next()) |line| : (index += 1) {
if (line.len == 0) {
continue;
}
const game = try Game.parse(line);
if (game.possible(target)) {
possible += index;
}
powers += game.red * game.blue * game.green;
}
return Solution{ .possible = possible, .powers = powers };
}
fn solveSample() !Solution {
const input =
\\Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
\\Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
\\Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
\\Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
\\Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
;
return solve(input, .{ .red = 12, .blue = 14, .green = 13 });
}
test "silver" {
const sln = try solveSample();
try std.testing.expectEqual(@as(u32, 8), sln.possible);
}
test "gold" {
const sln = try solveSample();
try std.testing.expectEqual(@as(u32, 2286), sln.powers);
}

145
src/day03.zig Normal file
View File

@ -0,0 +1,145 @@
const std = @import("std");
const util = @import("util.zig");
pub fn main() !void {
const input = @embedFile("data/day03.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.sum});
std.debug.print("{d}\n", .{sln.gears});
}
const Part = struct {
value: usize,
line: usize,
start: usize,
end: usize,
};
const Symbol = struct { char: u8, line: usize, offset: usize };
const Solution = struct {
sum: usize,
gears: usize,
};
fn absDiff(comptime T: type, a: T, b: T) T {
return @max(a, b) - @min(a, b);
}
fn solve(alloc: std.mem.Allocator, input: []const u8) !Solution {
var parts = std.ArrayList(Part).init(alloc);
var symbols = std.ArrayList(Symbol).init(alloc);
defer parts.deinit();
defer symbols.deinit();
// Accumulate parts and symbols.
var it = std.mem.splitScalar(u8, input, '\n');
var lnum: usize = 0;
while (it.next()) |line| : (lnum += 1) {
var current: Part = undefined;
var in_part = false;
var offset: usize = 0;
for (line) |char| {
switch (char) {
'0'...'9' => {
if (!in_part) {
current.line = lnum;
current.start = offset;
current.value = 0;
in_part = true;
}
current.value = current.value * 10 + (char - '0');
},
'.' => {
if (in_part) {
current.end = offset - 1;
try parts.append(current);
}
in_part = false;
},
else => {
if (in_part) {
current.end = offset - 1;
try parts.append(current);
}
in_part = false;
try symbols.append(.{ .char = char, .line = lnum, .offset = offset });
},
}
offset += 1;
}
// Handle number at the end of a line.
if (in_part) {
current.end = offset - 1;
try parts.append(current);
}
}
// Locate actual parts.
var sum: usize = 0;
var gear_sum: usize = 0;
var gears = std.AutoHashMap(Symbol, Part).init(alloc);
defer gears.deinit();
ploop: for (parts.items) |part| {
// std.debug.print("part {d}x{d}..{d}\n", .{ part.line, part.start, part.end });
for (symbols.items) |symbol| {
// std.debug.print("symbol {d}x{d}\n", .{ symbol.line, symbol.offset });
const inOffset = part.start <= symbol.offset + 1 and part.end + 1 >= symbol.offset;
const inLine = absDiff(usize, part.line, symbol.line) <= 1;
if (inOffset and inLine) {
// std.debug.print("part {d}x({d}..{d})={d}\n", .{ part.line, part.start, part.end, part.value });
sum += part.value;
// Detect gears.
if (symbol.char == '*') {
if (gears.get(symbol)) |other_gear| {
gear_sum += part.value * other_gear.value;
_ = gears.remove(symbol);
} else {
try gears.put(symbol, part);
}
}
continue :ploop;
}
}
}
return .{ .sum = sum, .gears = gear_sum };
}
test "silver" {
const input =
\\467..114..
\\...*......
\\..35..633.
\\......#...
\\617*......
\\.....+.58.
\\..592.....
\\......755.
\\...$.*....
\\664..598..
;
try std.testing.expectEqual(@as(usize, 4361), (try solve(std.testing.allocator, input)).sum);
}
test "gold" {
const input =
\\467..114..
\\...*......
\\..35..633.
\\......#...
\\617*......
\\.....+.58.
\\..592.....
\\......755.
\\...$.*....
\\.664.598..
;
try std.testing.expectEqual(@as(usize, 467835), (try solve(std.testing.allocator, input)).gears);
}

99
src/day04.zig Normal file
View File

@ -0,0 +1,99 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day04.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.value});
std.debug.print("{d}\n", .{sln.copies});
}
const Solution = struct {
value: usize,
copies: usize,
};
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
var value: usize = 0;
var card_counts = std.AutoHashMap(usize, usize).init(alloc);
defer card_counts.deinit();
var card: usize = 0;
var it = util.splitLines(input);
while (it.next()) |line| : (card += 1) {
if (line.len == 0) continue;
const sep = mem.indexOfScalar(u8, line, ':').?;
var line_it = util.split(line[sep + 1 ..], '|');
const winning = try util.parseIntsScalar(usize, alloc, line_it.next().?, .{});
defer alloc.free(winning);
const mine = try util.parseIntsScalar(usize, alloc, line_it.next().?, .{});
defer alloc.free(mine);
var matches: usize = 0;
for (mine) |n| {
if (mem.containsAtLeast(usize, winning, 1, &[_]usize{n})) {
matches += 1;
}
}
if (matches > 0) {
value += std.math.pow(usize, 2, matches - 1);
}
// Initialise with 1 copy of current card
const copies = blk: {
const entry = try card_counts.getOrPut(card);
if (entry.found_existing) {
entry.value_ptr.* += 1;
} else {
entry.value_ptr.* = 1;
}
break :blk entry.value_ptr.*;
};
// 1 additional card for each match, multiplied by copies of this card
for (1..matches + 1) |offset| {
const winning_card_entry = try card_counts.getOrPut(card + offset);
if (winning_card_entry.found_existing) {
winning_card_entry.value_ptr.* += copies;
} else {
winning_card_entry.value_ptr.* = copies;
}
}
}
var copy_count: usize = 0;
var copy_it = card_counts.valueIterator();
while (copy_it.next()) |v| {
copy_count += v.*;
}
return .{ .value = value, .copies = copy_count };
}
test "silver" {
const input =
\\Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
\\Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
\\Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
\\Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
\\Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
\\Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 13), sln.value);
}
test "gold" {
const input =
\\Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
\\Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
\\Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
\\Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
\\Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
\\Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 30), sln.copies);
}

223
src/day05.zig Normal file
View File

@ -0,0 +1,223 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day05.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln});
std.debug.print("{d}\n", .{sln});
}
fn Range(comptime T: type) type {
return struct {
start: T,
end: T,
const Self = @This();
/// Returns the intersection of two ranges.
pub fn intersect(self: Self, other: Self) ?Self {
const a = if (self.start < other.start) self else other;
const b = if (self.start < other.start) other else self;
if (a.start == a.end or b.start == b.end) return null; // Points don't intersect.
if (a.end < b.start) return null; // No intersection.
if (a.end > b.end) return b; // A is a superset of B.
return .{ .start = b.start, .end = a.end };
}
/// Returns the union of two ranges.
pub fn join(self: Self, other: Self) ?Self {
if (self.intersect(other) == null) return null; // Can't union disjoint ranges.
// Now guaranteed that they overlap.
return .{ .start = @min(self.start, other.start), .end = @max(self.end, other.end) };
}
};
}
const Map = struct {
offset: isize,
source: Range(usize),
};
const Component = struct {
stage: usize,
range: Range(usize),
};
fn mergeRanges(ranges: *std.ArrayList(Component)) void {
var i: usize = 0;
while (i < ranges.items.len) : (i += 1) {
var item = ranges.items[i];
var j = i + 1;
while (j < ranges.items.len) {
const next = ranges.items[j];
if (item.range.join(next.range)) |merged| {
const merged_item = .{ .stage = item.stage, .range = merged };
item = merged_item;
ranges.items[i] = merged_item;
_ = ranges.swapRemove(j);
} else {
j += 1;
}
}
}
}
fn solve(alloc: mem.Allocator, input: []const u8) !usize {
var it = util.splitLines(input);
const seed_line = it.next().?;
const seeds = try util.parseIntsScalar(usize, alloc, seed_line[mem.indexOfScalar(u8, seed_line, ':').? + 1 ..], .{});
defer alloc.free(seeds);
var ranges = std.ArrayList(Component).init(alloc);
defer ranges.deinit();
{
var index: usize = 0;
while (index < seeds.len) : (index += 2) {
try ranges.append(.{
.stage = 0,
.range = .{
.start = seeds[index],
.end = seeds[index] + seeds[index + 1],
},
});
}
}
var current_stage: usize = 0;
while (it.next()) |line| {
if (line.len == 0) {
for (ranges.items, 0..) |item, index| {
if (item.stage != current_stage) {
ranges.items[index] = .{ .stage = current_stage, .range = item.range };
}
}
mergeRanges(&ranges);
} else if (mem.endsWith(u8, line, "map:")) {
current_stage += 1;
} else {
const map_line = try util.parseIntsScalar(usize, alloc, line, .{});
defer alloc.free(map_line);
const map = Map{
.offset = @as(isize, @intCast(map_line[0])) - @as(isize, @intCast(map_line[1])),
.source = .{ .start = map_line[1], .end = map_line[1] + map_line[2] },
};
var i: usize = ranges.items.len;
while (i != 0) : (i -= 1) {
if (ranges.items[i - 1].stage == current_stage) continue;
const item = ranges.swapRemove(i - 1);
if (item.range.intersect(.{ .start = 0, .end = map.source.start })) |section| {
// Non-transitioning section.
try ranges.append(.{ .stage = item.stage, .range = section });
}
if (item.range.intersect(map.source)) |section| {
// Transitioning section.
try ranges.append(.{
.stage = current_stage,
.range = .{
.start = @intCast(@as(isize, @intCast(section.start)) + map.offset),
.end = @intCast(@as(isize, @intCast(section.end)) + map.offset),
},
});
}
if (item.range.intersect(.{ .start = map.source.end, .end = std.math.maxInt(usize) })) |section| {
// Non-transitioning section.
try ranges.append(.{ .stage = item.stage, .range = section });
}
}
}
}
var min_loc: usize = std.math.maxInt(usize);
for (ranges.items) |item| {
min_loc = @min(min_loc, item.range.start);
}
return min_loc;
}
test "silver" {
const input =
\\seeds: 79 14 55 13
\\
\\seed-to-soil map:
\\50 98 2
\\52 50 48
\\
\\soil-to-fertilizer map:
\\0 15 37
\\37 52 2
\\39 0 15
\\
\\fertilizer-to-water map:
\\49 53 8
\\0 11 42
\\42 0 7
\\57 7 4
\\
\\water-to-light map:
\\88 18 7
\\18 25 70
\\
\\light-to-temperature map:
\\45 77 23
\\81 45 19
\\68 64 13
\\
\\temperature-to-humidity map:
\\0 69 1
\\1 0 69
\\
\\humidity-to-location map:
\\60 56 37
\\56 93 4
;
_ = input;
// const sln = try solve(std.testing.allocator, input);
// try std.testing.expectEqual(@as(usize, 35), sln);
}
test "gold" {
const input =
\\seeds: 79 14 55 13
\\
\\seed-to-soil map:
\\50 98 2
\\52 50 48
\\
\\soil-to-fertilizer map:
\\0 15 37
\\37 52 2
\\39 0 15
\\
\\fertilizer-to-water map:
\\49 53 8
\\0 11 42
\\42 0 7
\\57 7 4
\\
\\water-to-light map:
\\88 18 7
\\18 25 70
\\
\\light-to-temperature map:
\\45 77 23
\\81 45 19
\\68 64 13
\\
\\temperature-to-humidity map:
\\0 69 1
\\1 0 69
\\
\\humidity-to-location map:
\\60 56 37
\\56 93 4
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 46), sln);
}

73
src/day06.zig Normal file
View File

@ -0,0 +1,73 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day06.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
var result: usize = 1;
{
var it = util.splitLines(input);
const times = try util.parseIntsScalar(usize, alloc, it.next().?[10..], .{});
const distances = try util.parseIntsScalar(usize, alloc, it.next().?[10..], .{});
defer alloc.free(times);
defer alloc.free(distances);
for (times, distances) |time, minDistance| {
var winning: usize = 0;
for (0..time - 1) |speed| {
const distance = speed * (time - speed);
if (distance > minDistance) winning += 1;
}
result *= winning;
}
}
var result2: usize = 1;
{
var it = util.splitLines(input);
var td: [2]usize = undefined;
for (0..td.len) |i| {
const line = try mem.replaceOwned(u8, alloc, it.next().?[10..], " ", "");
defer alloc.free(line);
td[i] = try std.fmt.parseInt(usize, line, 10);
}
var winning2: usize = 0;
for (0..td[0] - 1) |speed| {
const distance = speed * (td[0] - speed);
if (distance > td[1]) winning2 += 1;
}
result2 *= winning2;
}
return .{ .a = result, .b = result2 };
}
test "silver" {
const input =
\\Time: 7 15 30
\\Distance: 9 40 200
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 288), sln.a);
}
test "gold" {
const input =
\\Time: 7 15 30
\\Distance: 9 40 200
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 71503), sln.b);
}

132
src/day07.zig Normal file
View File

@ -0,0 +1,132 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day07.txt");
const part1 = try solve(util.gpa, input, jack);
const part2 = try solve(util.gpa, input, joker);
std.debug.print("{d}\n", .{part1});
std.debug.print("{d}\n", .{part2});
}
const Cards = [5]u8;
const Score = enum(u8) {
fiveKind,
fourKind,
fullHouse,
threeKind,
twoPair,
onePair,
high,
};
const Hand = struct {
cards: [5]u8,
bid: u32,
score: Score,
};
const jack = 10;
const joker = 0;
fn fixCards(cards: *[5]u8, jValue: u8) void {
for (cards, 0..) |card, i| {
cards[i] = switch (card) {
'A' => 13,
'K' => 12,
'Q' => 11,
'J' => jValue,
'T' => 9,
'2'...'9' => card - '1',
else => unreachable,
};
}
}
fn u8gt(context: void, a: u8, b: u8) bool {
_ = context;
return a > b;
}
fn scoreHand(cards: Cards) Score {
var jokers: usize = 0;
var dups: [14]u8 = undefined;
@memset(&dups, 0);
for (cards) |a| {
if (a == joker) {
jokers += 1;
} else {
dups[a] += 1;
}
}
mem.sort(u8, &dups, {}, u8gt);
return switch (dups[0] + jokers) {
5 => .fiveKind,
4 => .fourKind,
3 => if (dups[1] == 2) .fullHouse else .threeKind,
2 => if (dups[1] == 2) .twoPair else .onePair,
else => .high,
};
}
fn compareHand(context: void, a: Hand, b: Hand) bool {
_ = context;
if (a.score != b.score) {
return @intFromEnum(a.score) > @intFromEnum(b.score);
} else {
for (a.cards, b.cards) |cardA, cardB| {
if (cardA != cardB) return cardA < cardB;
}
}
unreachable;
}
fn solve(alloc: mem.Allocator, input: []const u8, jValue: u8) !u32 {
var hands = try std.ArrayList(Hand).initCapacity(alloc, 1001);
defer hands.deinit();
var it = util.splitLines(input);
while (it.next()) |line| {
if (line.len == 0) continue;
var cards = line[0..5].*;
fixCards(&cards, jValue);
const bid = try std.fmt.parseInt(u32, line[6..], 10);
const score = scoreHand(cards);
try hands.append(.{ .cards = cards, .bid = bid, .score = score });
}
mem.sort(Hand, hands.items, {}, compareHand);
var winnings: u32 = 0;
for (hands.items, 1..) |hand, rank| {
winnings += hand.bid * @as(u32, @intCast(rank));
}
return winnings;
}
test "silver" {
const input =
\\32T3K 765
\\T55J5 684
\\KK677 28
\\KTJJT 220
\\QQQJA 483
;
const sln = try solve(std.testing.allocator, input, jack);
try std.testing.expectEqual(@as(usize, 6440), sln);
}
test "gold" {
const input =
\\32T3K 765
\\T55J5 684
\\KK677 28
\\KTJJT 220
\\QQQJA 483
;
const sln = try solve(std.testing.allocator, input, joker);
try std.testing.expectEqual(@as(usize, 5905), sln);
}

104
src/day08.zig Normal file
View File

@ -0,0 +1,104 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day08.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
const Step = struct {
left: [3]u8,
right: [3]u8,
};
fn gcd(a: usize, b: usize) usize {
return if (b == 0) a else gcd(b, a % b);
}
fn lcm(ns: []u32) usize {
var acc: usize = ns[0];
for (ns[1..]) |n| {
const nsize = @as(usize, n);
acc = acc / gcd(acc, nsize) * nsize;
}
return acc;
}
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
var states = std.AutoHashMap([3]u8, Step).init(alloc);
defer states.deinit();
var it = util.splitLines(input);
const steps = it.next().?;
while (it.next()) |line| {
if (line.len == 0) continue;
try states.put(line[0..3].*, .{ .left = line[7..10].*, .right = line[12..15].* });
}
var a_nodes = std.AutoHashMap([3]u8, Step).init(alloc);
defer a_nodes.deinit();
var state_it = states.iterator();
while (state_it.next()) |entry| {
if (entry.key_ptr[2] == 'A') {
try a_nodes.put(entry.key_ptr.*, entry.value_ptr.*);
}
}
var all_steps = std.ArrayList(u32).init(alloc);
defer all_steps.deinit();
var a_it = a_nodes.iterator();
while (a_it.next()) |start| {
std.debug.print("{s}\n", .{start.key_ptr});
var position: [3]u8 = start.key_ptr.*;
var count: u32 = 0;
find: while (position[2] != 'Z') : (count += 1) {
const curr_state = states.get(position) orelse break :find; // ???????
position = if (steps[count % steps.len] == 'L') curr_state.left else curr_state.right;
}
try all_steps.append(count);
}
return .{ .a = 2, .b = lcm(all_steps.items) };
}
test "silver" {
const input =
\\RL
\\
\\AAA = (BBB, CCC)
\\BBB = (DDD, EEE)
\\CCC = (ZZZ, GGG)
\\DDD = (DDD, DDD)
\\EEE = (EEE, EEE)
\\GGG = (GGG, GGG)
\\ZZZ = (ZZZ, ZZZ)
;
_ = input;
// const sln = try solve(std.testing.allocator, input);
// try std.testing.expectEqual(@as(usize, 2), sln.a);
}
test "gold" {
const input =
\\LR
\\
\\11A = (11B, XXX)
\\11B = (XXX, 11Z)
\\11Z = (11B, XXX)
\\22A = (22B, XXX)
\\22B = (22C, 22C)
\\22C = (22Z, 22Z)
\\22Z = (22B, 22B)
\\XXX = (XXX, XXX)
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 6), sln.b);
}

80
src/day09.zig Normal file
View File

@ -0,0 +1,80 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day09.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: isize,
b: isize,
};
fn drv(alloc: mem.Allocator, seq: []const isize) ![]const isize {
var list = try std.ArrayList(isize).initCapacity(alloc, seq.len - 1);
for (0..seq.len - 1) |i| {
try list.append(seq[i + 1] - seq[i]);
}
return list.toOwnedSlice();
}
fn zeroed(seq: []const isize) bool {
for (seq) |n| {
if (n != 0) return false;
}
return true;
}
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
var it = util.splitLines(input);
var prev_sum: isize = 0;
var next_sum: isize = 0;
while (it.next()) |line| {
if (line.len == 0) continue;
var seqs = std.ArrayList([]const isize).init(alloc);
defer seqs.deinit();
// Find zero derivation
try seqs.append(try util.parseIntsScalar(isize, alloc, line, .{}));
while (!zeroed(seqs.items[seqs.items.len - 1])) {
try seqs.append(try drv(alloc, seqs.items[seqs.items.len - 1]));
}
// Extrapolate
var next: isize = 0;
var prev: isize = 0;
for (0..seqs.items.len) |i| {
const seq = seqs.items[seqs.items.len - i - 1];
prev = seq[0] - prev;
next += seq[seq.len - 1];
alloc.free(seq);
}
prev_sum += prev;
next_sum += next;
}
return .{ .a = next_sum, .b = prev_sum };
}
test "silver" {
const input =
\\0 3 6 9 12 15
\\1 3 6 10 15 21
\\10 13 16 21 30 45
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(isize, 114), sln.a);
}
test "gold" {
const input =
\\0 3 6 9 12 15
\\1 3 6 10 15 21
\\10 13 16 21 30 45
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(isize, 2), sln.b);
}

170
src/day10.zig Normal file
View File

@ -0,0 +1,170 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
const assert = std.debug.assert;
pub fn main() !void {
const input = @embedFile("data/day10.txt");
const sln = try solve(util.gpa, input);
try printPipes(std.io.getStdOut(), input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
const Direction = enum(u2) {
up,
down,
left,
right,
};
fn Walker(comptime T: type) type {
return struct {
r: T,
c: T,
facing: Direction,
fn init(r: T, c: T) @This() {
return .{ .r = r, .c = c, .facing = .up };
}
fn walk(self: *@This(), grid: [][]u8) void {
self.walkSpace(grid[self.r][self.c]);
}
fn walkSpace(self: *@This(), char: u8) void {
switch (char) {
'S' => {},
'|' => assert(self.facing == .up or self.facing == .down),
'-' => assert(self.facing == .left or self.facing == .right),
'L' => switch (self.facing) {
.down => self.turn(.right),
.left => self.turn(.up),
else => unreachable,
},
'J' => switch (self.facing) {
.down => self.turn(.left),
.right => self.turn(.up),
else => unreachable,
},
'7' => switch (self.facing) {
.up => self.turn(.left),
.right => self.turn(.down),
else => unreachable,
},
'F' => switch (self.facing) {
.up => self.turn(.right),
.left => self.turn(.down),
else => unreachable,
},
else => unreachable,
}
self.fwd();
}
fn move(self: *@This(), row: i32, col: i32) void {
self.*.r += row;
self.*.c += col;
}
fn fwd(self: *@This()) void {
switch (self.facing) {
.up => self.move(-1, 0),
.down => self.move(1, 0),
.left => self.move(0, -1),
.right => self.move(0, 1),
}
}
fn turn(self: *@This(), dir: Direction) void {
self.*.facing = dir;
}
};
}
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
_ = alloc;
var grid: [][]u8 = undefined; // try alloc.alloc(u8, mem.count(u8, input, &[_]u8{'\n'}));
var s_found = false;
var sr: u32 = 0;
var sc: u32 = 0;
var it = util.splitLines(input);
var curr_row: u32 = 0;
while (it.next()) |line| {
if (line.len == 0) continue;
// grid[curr_row] = line;
if (!s_found) {
if (mem.indexOfScalar(u8, line, 'S')) |col| {
_ = col;
sr = curr_row;
// sc = col;
s_found = true;
}
}
}
// Find actual starting position.
var walker = Walker(u32).init(sr, sc);
if (mem.indexOfScalar(u8, "|7F", grid[sr - 1][sc])) |_| {
walker.facing = .up;
} else if (mem.indexOfScalar(u8, "|JL", grid[sr + 1][sc])) |_| {
walker.facing = .down;
} else if (mem.indexOfScalar(u8, "-LF", grid[sr][sc - 1])) |_| {
walker.facing = .left;
} else if (mem.indexOfScalar(u8, "-J7", grid[sr][sc + 1])) |_| {
walker.facing = .right;
}
var steps: u32 = 1;
while (true) {
steps += 1;
if (grid[walker.r][walker.c] == 'S') break;
}
return .{ .a = steps / 2, .b = 0 };
}
fn printPipes(writer: anytype, input: []const u8) !void {
for (input) |char| {
_ = switch (char) {
'|' => try writer.write(""),
'-' => try writer.write(""),
'L' => try writer.write(""),
'J' => try writer.write(""),
'7' => try writer.write(""),
'F' => try writer.write(""),
'.' => try writer.write(" "),
'S' => try writer.write("o"),
'\n' => try writer.write("\n"),
else => unreachable,
};
}
}
test "silver" {
const input =
\\-L|F7
\\7S-7|
\\L|7||
\\-L-J|
\\L|-JF
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 6), sln.a);
}
test "gold" {
const input =
\\
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 0), sln.b);
}

155
src/day11.zig Normal file
View File

@ -0,0 +1,155 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day11.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
fn Point(comptime T: type) type {
return struct { x: T, y: T };
}
fn findGaps(comptime T: type, alloc: mem.Allocator, occupied: std.AutoHashMap(T, void)) ![]T {
var it = occupied.keyIterator();
var sorted_occupied = try std.ArrayList(T).initCapacity(alloc, occupied.count());
defer sorted_occupied.deinit();
while (it.next()) |item| {
try sorted_occupied.append(item.*);
}
mem.sort(T, sorted_occupied.items, {}, std.sort.asc(T));
var gaps = std.ArrayList(T).init(alloc);
errdefer gaps.deinit();
var gap: T = 0;
var i: usize = 0;
const max_occ = if (sorted_occupied.items.len != 0) std.mem.max(T, sorted_occupied.items) else 0;
while (gap < max_occ and i < sorted_occupied.items.len) : (gap += 1) {
if (sorted_occupied.items[i] != gap) {
try gaps.append(gap);
} else {
i += 1;
}
}
return gaps.toOwnedSlice();
}
fn expand(comptime T: type, alloc: mem.Allocator, galaxies: []Point(T), scale: T) !void {
var seen_cols = std.AutoHashMap(T, void).init(alloc);
defer seen_cols.deinit();
var seen_rows = std.AutoHashMap(T, void).init(alloc);
defer seen_rows.deinit();
for (galaxies) |galaxy| {
try seen_cols.put(galaxy.x, {});
try seen_rows.put(galaxy.y, {});
}
var gap_cols = try findGaps(T, alloc, seen_cols);
defer alloc.free(gap_cols);
var gap_rows = try findGaps(T, alloc, seen_rows);
defer alloc.free(gap_rows);
for (0..galaxies.len) |i| {
const galaxy = galaxies[i];
var col_offset: T = 0;
for (gap_cols) |gap| {
if (gap > galaxy.x) break;
col_offset += scale - 1;
}
var row_offset: T = 0;
for (gap_rows) |gap| {
if (gap > galaxy.y) break;
row_offset += scale - 1;
}
galaxies[i] = Point(T){ .x = galaxy.x + col_offset, .y = galaxy.y + row_offset };
}
}
fn absDiff(comptime T: type, a: T, b: T) T {
return @max(a, b) - @min(a, b);
}
fn solvePart(alloc: mem.Allocator, input: []const u8, scale: usize) !usize {
var galaxies = std.ArrayList(Point(usize)).init(alloc);
defer galaxies.deinit();
var it = util.splitLines(input);
var y: usize = 0;
while (it.next()) |line| {
if (line.len == 0) continue;
var x: usize = 0;
for (line) |char| {
switch (char) {
'.' => {},
'#' => try galaxies.append(Point(usize){ .x = x, .y = y }),
else => unreachable,
}
x += 1;
}
y += 1;
}
try expand(usize, alloc, galaxies.items, scale);
var distance: usize = 0;
for (galaxies.items, 0..) |a, i| {
for (galaxies.items[i + 1 .. galaxies.items.len]) |b| {
const d = absDiff(usize, a.x, b.x) + absDiff(usize, a.y, b.y);
distance += d;
}
}
return distance;
}
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
return .{ .a = try solvePart(alloc, input, 2), .b = try solvePart(alloc, input, 1_000_000) };
}
test "silver" {
const input =
\\...#......
\\.......#..
\\#.........
\\..........
\\......#...
\\.#........
\\.........#
\\..........
\\.......#..
\\#...#.....
;
const sln = try solvePart(std.testing.allocator, input, 2);
try std.testing.expectEqual(@as(usize, 374), sln);
}
test "gold" {
const input =
\\...#......
\\.......#..
\\#.........
\\..........
\\......#...
\\.#........
\\.........#
\\..........
\\.......#..
\\#...#.....
;
try std.testing.expectEqual(@as(usize, 1030), try solvePart(std.testing.allocator, input, 10));
try std.testing.expectEqual(@as(usize, 8410), try solvePart(std.testing.allocator, input, 100));
}

163
src/day12.zig Normal file
View File

@ -0,0 +1,163 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day12.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
fn hash(alloc: mem.Allocator, pattern: []const u8, groups: []const u8, curr_group: u8) ![]u8 {
var a = try std.ArrayList(u8).initCapacity(alloc, pattern.len + groups.len + 1);
try a.appendSlice(pattern);
try a.appendSlice(groups);
try a.append(curr_group);
return try a.toOwnedSlice();
}
var memo: std.StringHashMap(usize) = undefined;
fn clone(comptime T: type, alloc: mem.Allocator, s: []const T) ![]T {
var a = try alloc.alloc(T, s.len);
@memcpy(a, s);
return a;
}
fn solvePattern(alloc: mem.Allocator, pattern: []const u8, groups: []const u8, curr_group: u8) !usize {
const h = try hash(alloc, pattern, groups, curr_group);
if (memo.get(h)) |m| {
alloc.free(h);
return m;
}
if (pattern.len == 0) {
return if (groups.len == 0) 1 else 0;
}
const res: usize = b: {
switch (pattern[0]) {
'#' => {
// Too many #
if (groups.len == 0 or curr_group >= groups[0]) {
break :b 0;
}
// Special-case last #
if (pattern.len == 1 and groups.len == 1 and groups[0] == curr_group + 1) {
break :b 1;
}
break :b try solvePattern(alloc, pattern[1..], groups, curr_group + 1);
},
'.' => {
if (curr_group > 0) {
if (groups.len == 0 or curr_group != groups[0]) {
// Too many #
break :b 0;
} else {
// Group done
break :b try solvePattern(alloc, pattern[1..], groups[1..], 0);
}
} else {
// Not growing a group, skip.
break :b try solvePattern(alloc, pattern[1..], groups, 0);
}
},
'?' => {
const p1 = try clone(u8, alloc, pattern);
defer alloc.free(p1);
const p2 = try clone(u8, alloc, pattern);
defer alloc.free(p2);
p1[0] = '#';
p2[0] = '.';
break :b try solvePattern(alloc, p1, groups, curr_group) +
try solvePattern(alloc, p2, groups, curr_group);
},
else => unreachable,
}
};
try memo.put(h, res);
return res;
}
fn unfold(alloc: mem.Allocator, input: []const u8, sep: u8) ![]const u8 {
var buf = try std.ArrayList(u8).initCapacity(alloc, input.len * 5 + 5);
for (0..5) |i| {
if (i > 0 and sep > 0) try buf.append(sep);
try buf.appendSlice(input);
}
return try buf.toOwnedSlice();
}
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
memo = std.StringHashMap(usize).init(alloc);
defer memo.deinit();
var sum1: usize = 0;
var sum2: usize = 0;
var it = util.splitLines(input);
var i: usize = 1;
while (it.next()) |line| {
if (line.len == 0) continue;
std.debug.print("line {} ", .{i});
var lit = util.splitSpace(line);
const pattern = lit.next().?;
const raw_groups = lit.next().?;
const groups = try util.parseIntsScalar(u8, alloc, raw_groups, .{ .sep = ',' });
defer alloc.free(groups);
const unfolded_pattern = try unfold(alloc, pattern, '?');
const unfolded_groups = try unfold(alloc, groups, 0);
defer alloc.free(unfolded_pattern);
defer alloc.free(unfolded_groups);
sum1 += try solvePattern(alloc, pattern, groups, 0);
sum2 += try solvePattern(alloc, unfolded_pattern, unfolded_groups, 0);
std.debug.print("{} {}\n", .{ sum1, sum2 });
i += 1;
}
var mkeys = memo.keyIterator();
while (mkeys.next()) |k| {
alloc.free(k.*);
}
return .{ .a = sum1, .b = sum2 };
}
test "silver" {
const input =
\\???.### 1,1,3
\\.??..??...?##. 1,1,3
\\?#?#?#?#?#?#?#? 1,3,1,6
\\????.#...#... 4,1,1
\\????.######..#####. 1,6,5
\\?###???????? 3,2,1
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 21), sln.a);
}
test "gold" {
const input =
\\???.### 1,1,3
\\.??..??...?##. 1,1,3
\\?#?#?#?#?#?#?#? 1,3,1,6
\\????.#...#... 4,1,1
\\????.######..#####. 1,6,5
\\?###???????? 3,2,1
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 525152), sln.b);
}

136
src/day13.zig Normal file
View File

@ -0,0 +1,136 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day13.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
fn transpose(alloc: mem.Allocator, grid: []const []const u8) ![]const []const u8 {
std.debug.assert(grid.len > 0 and grid[0].len > 0);
var g = try alloc.alloc([]u8, grid[0].len);
for (0..grid[0].len) |i| {
g[i] = try alloc.alloc(u8, grid.len);
}
for (0..grid.len) |i| {
for (0..grid[i].len) |j| {
g[j][i] = grid[i][j];
}
}
return g;
}
fn findReflection(input: []const []const u8, err_tolerance: usize) ?usize {
if (input.len == 0) return 0;
const h = input.len;
const w = input[0].len;
for (0..h - 1) |scan| {
var offset: usize = 0;
var col: usize = 0;
var errors: usize = 0;
while (true) {
if (input[scan - offset][col] != input[scan + offset + 1][col]) {
errors += 1;
}
if (errors > err_tolerance + 1) {
break;
}
col = (col + 1) % w;
if (col == 0) offset += 1;
if (scan < offset or scan + offset + 1 >= h) {
if (errors == err_tolerance) return scan + 1;
break;
}
}
}
return null;
}
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
var curr_pattern = std.ArrayList([]const u8).init(alloc);
defer curr_pattern.deinit();
var it = util.splitLines(input);
var sums = [2]usize{ 0, 0 };
while (it.next()) |line| {
if (line.len == 0) {
const pattern = try curr_pattern.toOwnedSlice();
defer alloc.free(pattern);
for (0..2) |et| {
if (findReflection(pattern, et)) |score| {
sums[et] += 100 * score;
} else {
const t = try transpose(alloc, pattern);
sums[et] += findReflection(t, et) orelse unreachable;
// Ew.
for (t) |r| {
alloc.free(r);
}
alloc.free(t);
}
}
} else {
try curr_pattern.append(line);
}
}
return .{ .a = sums[0], .b = sums[1] };
}
test "silver" {
const input =
\\#.##..##.
\\..#.##.#.
\\##......#
\\##......#
\\..#.##.#.
\\..##..##.
\\#.#.##.#.
\\
\\#...##..#
\\#....#..#
\\..##..###
\\#####.##.
\\#####.##.
\\..##..###
\\#....#..#
\\
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 405), sln.a);
}
test "gold" {
const input =
\\#.##..##.
\\..#.##.#.
\\##......#
\\##......#
\\..#.##.#.
\\..##..##.
\\#.#.##.#.
\\
\\#...##..#
\\#....#..#
\\..##..###
\\#####.##.
\\#####.##.
\\..##..###
\\#....#..#
\\
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 400), sln.b);
}

94
src/day14.zig Normal file
View File

@ -0,0 +1,94 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day14.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
fn northWeight(alloc: mem.Allocator, grid: [][]u8) !usize {
var next_slot = try alloc.alloc(u32, grid[0].len);
defer alloc.free(next_slot);
@memset(next_slot, 0);
var weight: usize = 0;
for (grid, 0..) |line, row| {
for (line, 0..) |char, col| {
switch (char) {
'O' => {
weight += grid.len - next_slot[col];
next_slot[col] += 1;
},
'#' => next_slot[col] = @as(u32, @intCast(row)) + 1,
else => {},
}
}
}
return weight;
}
fn spin(grid: [][]u8, n: usize) void {
_ = grid;
for (0..n) |_| {}
}
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
var height = mem.count(u8, input, &[_]u8{'\n'});
if (input[input.len - 1] != '\n') height += 1;
var grid = try alloc.alloc([]u8, height);
defer alloc.free(grid);
var it = util.splitLines(input);
var row: u32 = 0;
while (it.next()) |line| {
if (line.len == 0) continue;
grid[row] = try alloc.alloc(u8, line.len);
@memcpy(grid[row], line);
row += 1;
}
const w = try northWeight(alloc, grid);
spin(grid, 1000000000);
const v = try northWeight(alloc, grid);
_ = v;
for (grid) |line| {
alloc.free(line);
}
return .{ .a = w, .b = 0 };
}
test "silver" {
const input =
\\O....#....
\\O.OO#....#
\\.....##...
\\OO.#O....O
\\.O.....O#.
\\O.#..O.#.#
\\..O..#O..O
\\.......O..
\\#....###..
\\#OO..#....
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 136), sln.a);
}
test "gold" {
const input =
\\
;
_ = input;
// const sln = try solve(std.testing.allocator, input);
// try std.testing.expectEqual(@as(usize, 0), sln.b);
}

91
src/day15.zig Normal file
View File

@ -0,0 +1,91 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day15.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
fn hash(input: []const u8) u32 {
var v: u32 = 0;
for (input) |c| {
v = ((v + c) * 17) % 256;
}
return v;
}
const SAHM = std.StringArrayHashMap(u8);
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
const trimmed = mem.trimRight(u8, input, "\n\r");
var hash_sum: u32 = 0;
var it = util.split(trimmed, ',');
while (it.next()) |line| {
if (line.len == 0) continue;
hash_sum += hash(line);
}
var boxes: [256]?SAHM = [_]?SAHM{null} ** 256;
it = util.split(trimmed, ',');
while (it.next()) |line| {
if (line.len == 0) continue;
const op = mem.indexOfAny(u8, line, "-=") orelse unreachable;
const label = line[0..op];
const label_hash = hash(label);
var box = &(boxes[label_hash] orelse init: {
var b = SAHM.init(alloc);
boxes[label_hash] = b;
break :init b;
});
switch (line[op]) {
'-' => _ = box.orderedRemove(label),
'=' => try box.put(label, try std.fmt.parseInt(u8, line[op + 1 ..], 10)),
else => unreachable,
}
}
var focus: usize = 0;
for (boxes, 1..) |maybe_box, box_n| {
var box = maybe_box orelse continue;
var box_it = box.iterator();
var lens_n: usize = 1;
while (box_it.next()) |entry| {
focus += box_n * lens_n * entry.value_ptr.*;
lens_n += 1;
}
box.deinit();
}
return .{ .a = hash_sum, .b = focus };
}
test "sample hash" {
try std.testing.expectEqual(@as(usize, 52), hash("HASH"));
}
test "silver" {
const input =
\\rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 1320), sln.a);
}
test "gold" {
const input =
\\rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 145), sln.b);
}

159
src/day16.zig Normal file
View File

@ -0,0 +1,159 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/day16.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
const Direction = enum {
up,
right,
down,
left,
fn x(self: @This()) isize {
return switch (self) {
.up, .down => 0,
.left => -1,
.right => 1,
};
}
fn y(self: @This()) isize {
return switch (self) {
.left, .right => 0,
.up => -1,
.down => 1,
};
}
fn rotate(self: @This(), nineties: usize) Direction {
return @enumFromInt((@intFromEnum(self) + nineties) % std.enums.values(@This()).len);
}
};
const Point = struct {
x: isize,
y: isize,
dir: Direction,
};
var states: std.AutoArrayHashMap(Point, void) = undefined;
fn run(alloc: mem.Allocator, grid: [][]u8, visited: [][]bool, init_x: isize, init_y: isize, init_dir: Direction) !void {
var x = init_x;
var y = init_y;
var dir = init_dir;
while (0 <= x and x < grid[0].len - 1 and 0 <= y and y < grid.len - 1) {
visited[@intCast(y)][@intCast(x)] = true;
var looped = try states.getOrPut(.{ .x = x, .y = y, .dir = dir });
if (looped.found_existing) break;
// std.debug.print("-----\n", .{});
// std.debug.print("{} {} {}\n", .{ x, y, dir });
// std.time.sleep(1_000_000_000);
switch (grid[@intCast(y)][@intCast(x)]) {
'.' => {
x += dir.x();
y += dir.y();
},
'|' => {
switch (dir) {
.up, .down => y += dir.y(),
.left, .right => {
try run(alloc, grid, visited, x, y - 1, .up);
try run(alloc, grid, visited, x, y + 1, .down);
},
}
},
'-' => {
switch (dir) {
.left, .right => x += dir.x(),
.up, .down => {
try run(alloc, grid, visited, x - 1, y, .left);
try run(alloc, grid, visited, x + 1, y, .right);
},
}
},
'/' => {
switch (dir) {
.left, .right => dir = dir.rotate(3),
.up, .down => dir = dir.rotate(1),
}
x += dir.x();
y += dir.y();
},
'\\' => {
switch (dir) {
.left, .right => dir = dir.rotate(1),
.up, .down => dir = dir.rotate(3),
}
x += dir.x();
y += dir.y();
},
else => unreachable,
}
}
}
fn solve(alloc: mem.Allocator, comptime input: []const u8) !Solution {
const trimmed = mem.trimRight(u8, input, "\n\r");
const w = mem.indexOfScalar(u8, trimmed, '\n') orelse unreachable;
const h = mem.count(u8, trimmed, "\n");
var visited = try util.allocGrid(bool, alloc, w, h, false);
const grid = try util.toGridOwned(alloc, trimmed, '\n');
states = std.AutoArrayHashMap(Point, void).init(alloc);
defer states.deinit();
try run(alloc, grid, visited, 0, 0, .right);
var energized: usize = 0;
for (visited) |row| {
for (row) |cell| {
if (cell) energized += 1;
}
}
for (grid) |row| {
alloc.free(row);
}
for (visited) |row| {
alloc.free(row);
}
alloc.free(grid);
alloc.free(visited);
return .{ .a = energized, .b = 0 };
}
const sample =
\\.|...\....
\\|.-.\.....
\\.....|-...
\\........|.
\\..........
\\.........\
\\..../.\\..
\\.-.-/..|..
\\.|....-|.\
\\..//.|....
;
test "silver" {
const sln = try solve(std.testing.allocator, sample);
try std.testing.expectEqual(@as(usize, 46), sln.a);
}
test "gold" {
// const sln = try solve(std.testing.allocator, input);
// try std.testing.expectEqual(@as(usize, 0), sln.b);
}

View File

@ -1,24 +0,0 @@
const std = @import("std");
pub fn main() !void {
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
// stdout is for the actual output of your application, for example if you
// are implementing gzip, then only the compressed bytes should be sent to
// stdout, not any debugging messages.
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
try stdout.print("Run `zig build test` to run the tests.\n", .{});
try bw.flush(); // don't forget to flush!
}
test "simple test" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}

65
src/util.zig Normal file
View File

@ -0,0 +1,65 @@
const std = @import("std");
const mem = std.mem;
var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){};
/// General purpose allocator.
pub const gpa = gpa_impl.allocator();
pub const ParseIntsOptions = struct {
base: u8 = 10,
sep: u8 = ' ',
};
/// Parses separated integers. Sequential separators are treated as one. Caller owns memory.
pub fn parseIntsScalar(comptime T: type, alloc: std.mem.Allocator, input: []const u8, opts: ParseIntsOptions) ![]T {
var items = std.ArrayList(T).init(alloc);
errdefer items.deinit();
var it = split(input, opts.sep);
while (it.next()) |i| {
if (i.len == 0) continue;
try items.append(try std.fmt.parseInt(T, i, opts.base));
}
return try items.toOwnedSlice();
}
/// Shortcut to splitScalar with a byte buffer.
pub fn split(buffer: []const u8, sep: u8) mem.SplitIterator(u8, .scalar) {
return mem.splitScalar(u8, buffer, sep);
}
pub fn splitLines(buffer: []const u8) mem.SplitIterator(u8, .scalar) {
return split(buffer, '\n');
}
pub fn splitSpace(buffer: []const u8) mem.SplitIterator(u8, .scalar) {
return split(buffer, ' ');
}
pub fn allocGrid(comptime T: type, alloc: mem.Allocator, w: usize, h: usize, init: T) ![][]T {
var grid = try alloc.alloc([]T, h);
for (0..h) |i| {
grid[i] = try alloc.alloc(T, w);
@memset(grid[i], init);
}
return grid;
}
pub fn toGridOwned(alloc: mem.Allocator, buffer: []const u8, sep: u8) ![][]u8 {
if (buffer.len == 0) return error.Empty;
const w = mem.indexOfScalar(u8, buffer, sep) orelse unreachable;
var h = mem.count(u8, buffer, &[_]u8{sep});
if (buffer[buffer.len - 1] != sep) h += 1;
var grid = try alloc.alloc([]u8, h);
var it = split(buffer, sep);
var i: usize = 0;
while (it.next()) |line| {
grid[i] = try alloc.alloc(u8, w);
@memcpy(grid[i], line);
i += 1;
}
return grid;
}

42
template.zig Normal file
View File

@ -0,0 +1,42 @@
const std = @import("std");
const util = @import("util.zig");
const mem = std.mem;
pub fn main() !void {
const input = @embedFile("data/dayXX.txt");
const sln = try solve(util.gpa, input);
std.debug.print("{d}\n", .{sln.a});
std.debug.print("{d}\n", .{sln.b});
}
const Solution = struct {
a: usize,
b: usize,
};
fn solve(alloc: mem.Allocator, input: []const u8) !Solution {
_ = alloc;
var it = util.splitLines(input);
while (it.next()) |line| {
if (line.len == 0) continue;
}
return .{ .a = 0, .b = 0 };
}
test "silver" {
const input =
\\
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 0), sln.a);
}
test "gold" {
const input =
\\
;
const sln = try solve(std.testing.allocator, input);
try std.testing.expectEqual(@as(usize, 0), sln.b);
}