From 6c5e1ea117652330af30369bc0bf4ad438ba52f9 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Wed, 20 Dec 2023 20:56:27 +0200 Subject: [PATCH] solve day 9 --- src/aoc.zig | 28 ++++++++-- src/day9.zig | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 3 ++ 3 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/day9.zig diff --git a/src/aoc.zig b/src/aoc.zig index a0e295e..7cb5388 100644 --- a/src/aoc.zig +++ b/src/aoc.zig @@ -9,6 +9,18 @@ pub const Input = struct { // For now only .uint is needed pub const Result = union(enum) { uint: u64, + int: i64, + + fn value(self: Result, comptime T: type) ?T { + inline for (@typeInfo(Result).Union.fields, 0..) |field, i| { + if (field.type != T) continue; + + if (@intFromEnum(self) == i) { + return @field(self, field.name); + } + } + return null; + } }; pub const Solver = *const fn(input: *Input) anyerror!Result; @@ -17,9 +29,17 @@ pub const Day = struct { part2: ?Solver }; -pub fn expectAnswerUInt(solver: Solver, answer: u64, lines: []const []const u8) !void { +fn expectAnswer(comptime T: type, solver: Solver, expected: T, lines: []const []const u8) !void { var input = Input{ .lines = lines, .allocator = std.testing.allocator }; - var actual = try solver(&input); - var expected: u64 = answer; - try std.testing.expectEqual(expected, actual.uint); + var result: Result = try solver(&input); + var actual = result.value(T) orelse return error.ResultTypeMismatch; + try std.testing.expectEqual(expected, actual); +} + +pub fn expectAnswerUInt(solver: Solver, answer: u64, lines: []const []const u8) !void { + try expectAnswer(u64, solver, answer, lines); +} + +pub fn expectAnswerInt(solver: Solver, answer: i64, lines: []const []const u8) !void { + try expectAnswer(i64, solver, answer, lines); } diff --git a/src/day9.zig b/src/day9.zig new file mode 100644 index 0000000..049a503 --- /dev/null +++ b/src/day9.zig @@ -0,0 +1,145 @@ +const aoc = @import("./aoc.zig"); +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const History = std.ArrayList(i32); +const HistoryChanges = std.ArrayList([]i32); +const Input = struct { + histories: std.ArrayList(History), + + fn deinit(self: *Input) void { + for (self.histories.items) |history| { + history.deinit(); + } + self.histories.deinit(); + } +}; + +fn parse_history(allocator: Allocator, line: []const u8) !History { + var history = History.init(allocator); + var tokens = std.mem.tokenizeScalar(u8, line, ' '); + while (tokens.next()) |token| { + const number = try std.fmt.parseInt(i32, token, 10); + try history.append(number); + } + return history; +} + +fn parse_input(allocator: Allocator, lines: []const []const u8) !Input { + var histories = std.ArrayList(History).init(allocator); + errdefer histories.deinit(); + + for (lines) |line| { + const history = try parse_history(allocator, line); + try histories.append(history); + } + + return Input{ .histories = histories }; +} + +fn is_all_zeros(numbers: []i32) bool { + for (numbers) |num| { + if (num != 0) { + return false; + } + } + return true; +} + +fn calc_changes(result: []i32, numbers: []i32) void { + assert(result.len >= numbers.len-1); + for (0..(numbers.len-1)) |i| { + result[i] = numbers[i+1] - numbers[i]; + } +} + +fn all_changes_of_history(allocator: Allocator, history: []i32) !HistoryChanges { + const max_changes_len: usize = history.len-1; + assert(max_changes_len >= 1); + + var changes = HistoryChanges.init(allocator); + errdefer free_history_changes(changes); + + try changes.append(try allocator.alloc(i32, history.len-1)); + calc_changes(changes.items[0], history); + + for (1..max_changes_len) |i| { + if (is_all_zeros(changes.items[i-1])) break; + + try changes.append(try allocator.alloc(i32, history.len-i-1)); + calc_changes(changes.items[i], changes.items[i-1]); + } + + return changes; +} + +fn free_history_changes(changes: HistoryChanges) void { + for (changes.items) |row| { + changes.allocator.free(row); + } + changes.deinit(); +} + +pub fn part1(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + var parsed = try parse_input(allocator, input.lines); + defer parsed.deinit(); + + var result: i32 = 0; + for (parsed.histories.items) |history| { + const numbers = history.items; + + var changes = try all_changes_of_history(allocator, numbers); + defer free_history_changes(changes); + + var prediction: i32 = numbers[numbers.len-1]; + for (changes.items) |change_row| { + prediction += change_row[change_row.len-1]; + } + + result += prediction; + } + + return .{ .int = result }; +} + +pub fn part2(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + var parsed = try parse_input(allocator, input.lines); + defer parsed.deinit(); + + var result: i32 = 0; + for (parsed.histories.items) |history| { + const numbers = history.items; + + var changes = try all_changes_of_history(allocator, numbers); + defer free_history_changes(changes); + + var prediction: i32 = 0; + for (0..changes.items.len) |i| { + const change_row = changes.items[changes.items.len-i-1]; + prediction = change_row[0] - prediction; + } + + prediction = numbers[0] - prediction; + + result += prediction; + } + + return .{ .int = result }; +} + +const example_input = [_][]const u8{ + "0 3 6 9 12 15", + "1 3 6 10 15 21", + "10 13 16 21 30 45", +}; + +test "part 1 example" { + try aoc.expectAnswerInt(part1, 114, &example_input); +} + +test "part 2 example" { + try aoc.expectAnswerInt(part2, 2, &example_input); +} diff --git a/src/main.zig b/src/main.zig index 7b288a1..0b9b452 100644 --- a/src/main.zig +++ b/src/main.zig @@ -29,6 +29,7 @@ const Days = [_]aoc.Day{ create_day(@import("day6.zig")), create_day(@import("day7.zig")), create_day(@import("day8.zig")), + create_day(@import("day9.zig")), }; fn kilobytes(count: u32) u32 { @@ -88,6 +89,7 @@ fn run(allocator: Allocator, args: *cli) !u8 { switch (result) { .uint => std.debug.print("{}\n", .{result.uint}), + .int => std.debug.print("{}\n", .{result.int}), } const duration = end_time - start_time; @@ -180,4 +182,5 @@ test { _ = @import("day6.zig"); _ = @import("day7.zig"); _ = @import("day8.zig"); + _ = @import("day9.zig"); }