Compare commits

...

2 Commits

Author SHA1 Message Date
b863e1b2aa solve day 3 2023-12-08 22:03:02 +02:00
59ac1720b9 add framework for adding solutions 2023-12-08 20:27:03 +02:00
7 changed files with 349 additions and 29 deletions

20
src/aoc.zig Normal file
View File

@ -0,0 +1,20 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const Input = struct {
allocator: Allocator,
lines: [][]const u8
};
pub const Result = union(enum) {
float: f32,
uint: u32,
int: i32,
text: [64:0]u8
};
pub const Solver = *const fn(input: *Input) anyerror!Result;
pub const Day = struct {
part1: ?Solver,
part2: ?Solver
};

63
src/cli.zig Normal file
View File

@ -0,0 +1,63 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Self = @This();
allocator: Allocator,
day: u32,
part: u32,
input_file: []u8,
fn print_usage(program: []const u8) void {
std.debug.print("Usage: {s} <day> <part> [input.txt]\n", .{program});
}
pub fn init(allocator: Allocator) !Self {
var args = std.process.args();
const program = try allocator.dupe(u8, args.next().?);
defer allocator.free(program);
var day: u32 = 0;
var part: u32 = 0;
var input_file = try allocator.dupe(u8, "input.txt");
var i: u32 = 0;
while (args.next()) |arg| {
if (i == 0) {
day = std.fmt.parseInt(u32, arg, 10) catch {
std.debug.print("ERROR: Invalid day '{s}'\n", .{arg});
return error.cli;
};
} else if (i == 1) {
part = std.fmt.parseInt(u32, arg, 10) catch {
std.debug.print("ERROR: Invalid part '{s}'\n", .{arg});
return error.cli;
};
if (!(part == 1 or part == 2)) {
std.debug.print("ERROR: Part can only be 1 or 2\n", .{});
return error.cli;
}
} else if (i == 2) {
allocator.free(input_file);
input_file = try allocator.dupe(u8, arg);
}
i += 1;
}
if (i < 2) {
print_usage(program);
return error.cli;
}
return Self{
.allocator = allocator,
.day = day,
.part = part,
.input_file = input_file
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.input_file);
}

View File

@ -1,8 +1,12 @@
const std = @import("std");
const aoc = @import("./aoc.zig");
const Allocator = std.mem.Allocator;
const digits = [_][]const u8{ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
pub fn part1(lines: [][]const u8) u32 {
pub fn part1(input: *aoc.Input) !aoc.Result {
const lines = input.lines;
var sum: u32 = 0;
for (lines) |line| {
var first_digit: u8 = 0;
@ -23,10 +27,12 @@ pub fn part1(lines: [][]const u8) u32 {
sum += number;
}
return sum;
return .{ .uint = sum };
}
pub fn part2(lines: [][]const u8) u32 {
pub fn part2(input: *aoc.Input) !aoc.Result {
const lines = input.lines;
var sum: u32 = 0;
for (lines) |line| {
var first_digit: u8 = 0;
@ -84,23 +90,24 @@ pub fn part2(lines: [][]const u8) u32 {
sum += number;
}
return sum;
return .{ .uint = sum };
}
test "part 1 example" {
var input = [_][]const u8{
var lines = [_][]const u8{
"1abc2",
"pqr3stu8vwx",
"a1b2c3d4e5f",
"treb7uchet",
};
var actual = part1(&input);
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 142;
try std.testing.expectEqual(expected, actual);
try std.testing.expectEqual(expected, actual.uint);
}
test "part 2 example" {
var input = [_][]const u8{
var lines = [_][]const u8{
"two1nine",
"eightwothree",
"abcone2threexyz",
@ -109,7 +116,8 @@ test "part 2 example" {
"zoneight234",
"7pqrstsixteen",
};
var actual = part2(&input);
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 281;
try std.testing.expectEqual(expected, actual);
try std.testing.expectEqual(expected, actual.uint);
}

View File

@ -1,4 +1,5 @@
const std = @import("std");
const aoc = @import("./aoc.zig");
const Allocator = std.mem.Allocator;
const Cube = enum { Red, Green, Blue };
@ -49,7 +50,10 @@ fn parse_games(allocator: Allocator, lines: [][]const u8) ![]Game {
return games;
}
pub fn part1(allocator: Allocator, lines: [][]const u8) !u32 {
pub fn part1(input: *aoc.Input) !aoc.Result {
const lines = input.lines;
const allocator = input.allocator;
var games = try parse_games(allocator, lines);
defer allocator.free(games);
@ -58,7 +62,6 @@ pub fn part1(allocator: Allocator, lines: [][]const u8) !u32 {
const max_blue = 14;
var sum: u32 = 0;
for (1.., games) |idx, game| {
var possible = true;
for (game.sets[0..game.set_count]) |cube_set| {
if ((cube_set.get(.Red) > max_red) or (cube_set.get(.Blue) > max_blue) or (cube_set.get(.Green) > max_green)) {
@ -72,10 +75,12 @@ pub fn part1(allocator: Allocator, lines: [][]const u8) !u32 {
}
}
return sum;
return .{ .uint = sum };
}
pub fn part2(allocator: Allocator, lines: [][]const u8) !u32 {
pub fn part2(input: *aoc.Input) !aoc.Result {
const lines = input.lines;
const allocator = input.allocator;
var games = try parse_games(allocator, lines);
defer allocator.free(games);
@ -94,31 +99,33 @@ pub fn part2(allocator: Allocator, lines: [][]const u8) !u32 {
sum += max_red*max_green*max_blue;
}
return sum;
return .{ .uint = sum };
}
test "part 1 example" {
var input = [_][]const u8{
var lines = [_][]const u8{
"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"
};
var actual = try part1(std.testing.allocator, &input);
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 8;
try std.testing.expectEqual(expected, actual);
try std.testing.expectEqual(expected, actual.uint);
}
test "part 2 example" {
var input = [_][]const u8{
var lines = [_][]const u8{
"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"
};
var actual = try part2(std.testing.allocator, &input);
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 2286;
try std.testing.expectEqual(expected, actual);
try std.testing.expectEqual(expected, actual.uint);
}

146
src/day3.zig Normal file
View File

@ -0,0 +1,146 @@
const aoc = @import("./aoc.zig");
const std = @import("std");
const Point = @import("./point.zig").Point;
const PointU32 = Point(u32);
const PointList = std.ArrayList(PointU32);
fn find_number_start(line: []const u8, from: u32) u32 {
var i = from;
while (i > 0) : (i -= 1) {
if (!std.ascii.isDigit(line[i-1])) { break; }
}
return i;
}
fn find_number_end(line: []const u8, from: u32) u32 {
var i = from;
while (i < line.len-1) : (i += 1) {
if (!std.ascii.isDigit(line[i+1])) { break; }
}
return i+1;
}
fn append_unique(list: *PointList, point: PointU32) !void {
for (list.items) |item| {
if (item.eql(&point)) { return; }
}
try list.append(point);
}
fn find_neighbouring_numbers(found_list: *PointList, lines: [][]const u8, x: usize, y: usize) !void {
const neighbours = .{
.{ -1, -1 }, .{ 0, -1 }, .{ 1, -1 },
.{ -1, 0 }, .{ 1, 0 },
.{ -1, 1 }, .{ 0, 1 }, .{ 1, 1 },
};
inline for (neighbours) |offset| {
const nx = @as(i32, @intCast(x)) + offset[0];
const ny = @as(i32, @intCast(y)) + offset[1];
if ((nx >= 0 or ny >= 0 or nx < lines[y].len or ny < lines.len)) {
const nx_u32: u32 = @intCast(nx);
const ny_u32: u32 = @intCast(ny);
if (std.ascii.isDigit(lines[ny_u32][nx_u32])) {
const number_start = find_number_start(lines[ny_u32], nx_u32);
try append_unique(found_list, .{ .x = number_start, .y = ny_u32 });
}
}
}
}
pub fn part1(input: *aoc.Input) !aoc.Result {
const lines = input.lines;
const allocator = input.allocator;
var numbers = PointList.init(allocator);
defer numbers.deinit();
for (lines, 0..) |line, y| {
for (line, 0..) |c, x| {
if (std.ascii.isDigit(c) or c == '.') { continue; }
try find_neighbouring_numbers(&numbers, lines, x, y);
}
}
var sum: u32 = 0;
for (numbers.items) |number_pos| {
const line = lines[number_pos.y];
const number_end = find_number_end(line, number_pos.x);
const number_str = line[number_pos.x..number_end];
sum += try std.fmt.parseInt(u32, number_str, 10);
}
return .{ .uint = sum };
}
pub fn part2(input: *aoc.Input) !aoc.Result {
const lines = input.lines;
const allocator = input.allocator;
var sum: u32 = 0;
for (lines, 0..) |line, y| {
for (line, 0..) |c, x| {
if (c != '*') { continue; }
var numbers = PointList.init(allocator);
defer numbers.deinit();
try find_neighbouring_numbers(&numbers, lines, x, y);
if (numbers.items.len != 2) continue;
var gear_ratio: u32 = 1;
for (numbers.items) |number_pos| {
const number_line = lines[number_pos.y];
const number_end = find_number_end(number_line, number_pos.x);
const number_str = number_line[number_pos.x..number_end];
const number = try std.fmt.parseInt(u32, number_str, 10);
gear_ratio *= number;
}
sum += gear_ratio;
}
}
return .{ .uint = sum };
}
test "part 1 example" {
var lines = [_][]const u8{
"467..114..",
"...*......",
"..35..633.",
"......#...",
"617*......",
".....+.58.",
"..592.....",
"......755.",
"...$.*....",
".664.598.."
};
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 4361;
try std.testing.expectEqual(expected, actual.uint);
}
test "part 2 example" {
var lines = [_][]const u8{
"467..114..",
"...*......",
"..35..633.",
"......#...",
"617*......",
".....+.58.",
"..592.....",
"......755.",
"...$.*....",
".664.598.."
};
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 467835;
try std.testing.expectEqual(expected, actual.uint);
}

View File

@ -1,17 +1,61 @@
const std = @import("std");
const aoc = @import("./aoc.zig");
const cli = @import("./cli.zig");
const StringArray = std.ArrayList([]const u8);
const DayHashMap = std.StringHashMap(aoc.Day);
const Day1 = @import("./day1.zig");
const Day2 = @import("./day2.zig");
fn create_day(comptime module: anytype) aoc.Day {
var part1: ?aoc.Solver = null;
if (@hasDecl(module, "part1")) {
part1 = @field(module, "part1");
}
pub fn main() !void {
var part2: ?aoc.Solver = null;
if (@hasDecl(module, "part2")) {
part2 = @field(module, "part2");
}
return .{ .part1 = part1, .part2 = part2 };
}
const Days = [_]aoc.Day{
create_day(@import("day1.zig")),
create_day(@import("day2.zig")),
create_day(@import("day3.zig"))
};
pub fn main() !u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer {
if (gpa.deinit() == .leak) @panic("Leaked memory");
}
var file = try std.fs.cwd().openFile("input.txt", .{});
var args = cli.init(allocator) catch |err| switch (err) {
error.cli => return 255,
else => return err
} ;
defer args.deinit();
if (args.day > Days.len) {
std.debug.print("ERROR: Day {} does not exist\n", .{args.day});
return 255;
}
var day = Days[args.day-1];
if ((args.part == 1 and day.part1 == null) or (args.part == 2 and day.part2 == null)) {
std.debug.print("ERROR: Part {} for day {} does not exist\n", .{args.part, args.day});
return 255;
}
var file = std.fs.cwd().openFile(args.input_file, .{}) catch |err| switch (err) {
error.FileNotFound => {
std.debug.print("ERROR: Input file '{s}' does not exist\n", .{args.input_file});
return 255;
},
else => return err
};
defer file.close();
var buf_reader = std.io.bufferedReader(file.reader());
@ -31,9 +75,29 @@ pub fn main() !void {
try lines.append(try allocator.dupe(u8, trimmed_line));
}
// std.debug.print("day1: {d}\n", .{Day1.part1(lines.items)});
// std.debug.print("day2: {d}\n", .{Day1.part2(lines.items)});
var input = aoc.Input{ .allocator = allocator, .lines = lines.items };
std.debug.print("day1: {d}\n", .{try Day2.part1(allocator, lines.items)});
std.debug.print("day2: {d}\n", .{try Day2.part2(allocator, lines.items)});
var result: aoc.Result = undefined;
if (args.part == 1) {
result = try day.part1.?(&input);
} else if (args.part == 2) {
result = try day.part2.?(&input);
} else {
unreachable;
}
switch (result) {
.uint => std.debug.print("{}\n", .{result.uint}),
.float => std.debug.print("{}\n", .{result.float}),
.int => std.debug.print("{}\n", .{result.int}),
.text => std.debug.print("{s}\n", .{result.text}),
}
return 0;
}
test {
_ = @import("day1.zig");
_ = @import("day2.zig");
_ = @import("day3.zig");
}

12
src/point.zig Normal file
View File

@ -0,0 +1,12 @@
pub fn Point(comptime T: type) type {
return struct {
const Self = @This();
x: T,
y: T,
pub fn eql(self: *const Self, other: *const Self) bool {
return self.x == other.x and self.y == other.y;
}
};
}