Advent of Code 2023: Day 3

This post is part of a number that I've written as I complete Advent of Code exercises. The purpose is to practice taking notes for other people as I solve a problem, and to get used to writing and publishing a post on a nearly daily basis.

Part 1

A part is missing from a gondola. If you add up all the parts in the schematic, then you should be able to find what's missing.

The schematic consists of visual representation of the engine:

467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598..

In this schematic two numbers are not part numbers because they aren't adjacent to symbols: 114 and 58. The goal is to sum all of the part numbers.

Solution

This is interesting because the bounding box of a part number is dependent on its value. Big numbers cover more area.

I'm first going to create two tables, one for symbols and the other for numbers. The specific symbols don't matter right now, so it'll just be a list of coordinates, and the coordinate of a number will be the right most column. The size can be calculated from its value.

const Schematic = struct { symbols: List(Coordinate), numbers: Map(Coordinate, u32), };

The naive solution is to create a bounding box of possible coordinates that a symbol would have to exist in, in order for a number to be a part number, then check every single symbol if they exist in that bounding box.

My first answer of 347578 was too low.

Debugging

First what I'll do is write some tests to ensure there's no off-by-one bugs in the bounds checking functions.

Okay so I found the vital mistake in my bounds checking function:

fn is_inside(coord: Coordinate, box: BoundingBox) bool { return coord.row >= box.origin.row and coord.row < (box.origin.row + box.width) and coord.col >= box.origin.col and coord.col < (box.origin.col + box.height); }

The issue with this function is that box.width and box.height are swapped. Must have been a mixup from when I was thinking in x,y coordinates instead of row,col.

Still though! the answer is too low. I'm going to resort to logging so that I can get a bird's eye view and maybe pick something out.

Okay so I found my next problem. I added an assert that crashes the program when it finds a symbol "inside" a text block. Looks like we have a parsing problem. During this process I changed the symbols to a map so I could record the specific symbol, and see if there was anything funky there:

info: part number 452 located at a.Coordinate{ .row = 0, .col = 52 } is associated with symbol: '/' at a.Coordinate{ .row = 1, .col = 53 } info: part number 712 located at a.Coordinate{ .row = 0, .col = 73 } is associated with symbol: '*' at a.Coordinate{ .row = 1, .col = 73 } info: part number 646 located at a.Coordinate{ .row = 0, .col = 97 } is associated with symbol: '*' at a.Coordinate{ .row = 1, .col = 97 } info: part number 1 located at a.Coordinate{ .row = 0, .col = 104 } is associated with symbol: '*' at a.Coordinate{ .row = 1, .col = 103 } info: part number 958 located at a.Coordinate{ .row = 0, .col = 117 } is associated with symbol: '*' at a.Coordinate{ .row = 1, .col = 117 } info: part number 661 located at a.Coordinate{ .row = 1, .col = 37 } is associated with symbol: '-' at a.Coordinate{ .row = 1, .col = 39 } thread 8405352 panic: reached unreachable code /Users/mattnite/zig/0.11.0/files/lib/std/debug.zig:343:14: 0x100148d2f in assert (three_a) if (!ok) unreachable; // assertion failure ^ /Users/mattnite/code/aoc-2023/src/03_gear_ratios/a.zig:180:33: 0x10014de13 in main (three_a) std.debug.assert(!symbol_coord.is_inside(text)); ^ /Users/mattnite/zig/0.11.0/files/lib/std/start.zig:574:37: 0x10014ee6b in main (three_a) const result = root.main() catch |err| { ^ ???:?:?: 0x183a410df in ??? (???) ???:?:?: 0x4c29ffffffffffff in ??? (???) fish: Job 1, './zig-out/bin/three_a' terminated by signal SIGABRT (Abort)

Looking at the data, that number, 661, should actually start at column 34 and not 37. AH! there it is:

pub fn main() !void { // ... while (line_it.next()) |line| : (row += 1) { while (col < line.len) { switch (line[col]) { '1'...'9' => { const start_col = col; while (col < line.len and std.ascii.isDigit(line[col])) : (col += 1) {} const num = try std.fmt.parseInt(u32, line[start_col..col], 10); try schematic.numbers.put(.{ .row = row, .col = col, }, num); }, // ... } } } }

When I'm adding the number entry, I'm using the column iterator value, instead of the starting column value for the number.

That does the trick.

Part 2

A gear is any * symbol that is adjacent to exactly two part numbers. Its gear ration is the result of multiplying those two numbers together. For the next challenge I need to find the gear ratio of every gear and add them all up.

Solution

I'm going to copy over most of the code, however we're going to convert symbols to gears and associate each coordinate with a gear ratio and the count of part numbers. We do a naive scan of the numbers, and for the adjacent ones we increment the count, and accumulate the ratio using *=. Then all that's left is ripping through and summing all the entries with exactly two numbers.

Worked first try.