From 43982a67a11e0a5a4afc7e660d10e6ab112cbac2 Mon Sep 17 00:00:00 2001 From: Jack Jackson Date: Fri, 27 Dec 2024 20:14:09 -0800 Subject: [PATCH] Solution for 08-01 --- NOTES.md | 8 ++- inputs/08/real.txt | 50 +++++++++++++++++ inputs/08/test.txt | 12 ++++ scratch.zig | 29 +++++----- solutions/08.zig | 134 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 inputs/08/real.txt create mode 100644 inputs/08/test.txt create mode 100644 solutions/08.zig diff --git a/NOTES.md b/NOTES.md index ba26f4b..93f7969 100644 --- a/NOTES.md +++ b/NOTES.md @@ -13,6 +13,7 @@ Notes, thoughts, or questions that arose as I implemented the solutions. Hopeful * [Continue expressions](https://ziglang.org/documentation/master/#while) - don't need to remember to put the index-incrementing code at the end of every branch! * Great powerful `switch` syntax (though not as powerful as Rust's) * Labelled loops - _usually_ should be avoided, but helpful on occasion! +* `defer` - though, unlike in GoLang where it's a nice-to-have that allows one to do cleanup, here it is _absolutely essential_ for all the manual `deinit`s and `free`s # Things that I've found missing from this language @@ -22,6 +23,7 @@ Hmmmm, right now it seems even worse than GoLang. Though the Error handling is _ * [String equality](https://nofmal.github.io/zig-with-example/string-handling/#string-equal) * Switching on strings * [Iterating over values of an enum](https://zig.guide/language-basics/enums) - [this](https://ziggit.dev/t/iterating-over-a-packed-enum/6530) suggests that it's possible, but testing indicates that that only works at comptime. +* [Sets](https://github.com/ziglang/zig/issues/6919) (though, as the top comment points out, that's not _too_ bad as you can abuse a HashMap for it) ## Not "missing", but... @@ -198,4 +200,8 @@ fn accumulate() ![]u32 { @memcpy(response, list.items); return response; } -``` \ No newline at end of file +``` + +## What's the point in `HashMap.getOrPut`? + +`getOrPut` _doesn't_ actually `put` anything, it _only_ `get`s. See https://ziggit.dev/t/whats-the-point-in-hashmap-getorput/7547. \ No newline at end of file diff --git a/inputs/08/real.txt b/inputs/08/real.txt new file mode 100644 index 0000000..4cfb162 --- /dev/null +++ b/inputs/08/real.txt @@ -0,0 +1,50 @@ +..............U.............c.....3............... +.....p.........F.................................. +.....m..7....................4x............3...... +..e.............F..........c...YH..3.............. +.......e...................................c..E..8 +................a...U................8............ +..............................4.F...8....x........ +............7.....4............Hc..E.......x...... +........p..............................E.......... +.............U.e....................x....t........ +.7..........................Z.H....g.............. +.........7..m.....S.........................E..... +...F.....p...........6...SY....................... +.................6..k...................g......... +..........m......a........................g....... +.......M.......................................g.. +..............a............Y....C........H........ +....u.......6........a.........C.GY............... +.....M..................S......................2.. +..........M........S.....................2........ +........M.......................5.........z..f.... +.....................................Z........t.2. +..........6....................................... +......................................G........... +.........................A.........G9....Z........ +........................C......................... +.....k......................G......z..t........... +.......k......................zs....f........5...9 +................h........................9....2... +.............h.....0...........f.....K..ZX........ +..................................f............... +.......1....................9.........Xz.......... +...............1......B.s......X.................. +............h...............B..................... +..T.........k..................b.................. +...............u.................................. +.........u.............h..................0....... +..............y................................... +...............................t....X......5...... +.................A............................5... +................u..................s.............. +.T..........b....y................................ +............y............................K........ +..1...............................s....B.......... +..............Ay.............B...P................ +..........T.......................K...........0... +.............T..................P.........K....... +......A....P...................................... +....b.........1................................... +.........b................................P....... diff --git a/inputs/08/test.txt b/inputs/08/test.txt new file mode 100644 index 0000000..78a1e91 --- /dev/null +++ b/inputs/08/test.txt @@ -0,0 +1,12 @@ +............ +........0... +.....0...... +.......0.... +....0....... +......A..... +............ +............ +........A... +.........A.. +............ +............ diff --git a/scratch.zig b/scratch.zig index 6b00d7c..8e092c5 100644 --- a/scratch.zig +++ b/scratch.zig @@ -1,25 +1,26 @@ const std = @import("std"); const print = std.debug.print; -test "Demo accumulation" { - const accumulated = try accumulate(); - print("DEBUG - accumulated values are {any}\n", .{accumulated}); -} +const expect = @import("std").testing.expect; -fn accumulate() ![]u32 { +test "Len of an iterator is not the same as size" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); - var list = std.ArrayList(u32).init(allocator); - defer list.deinit(); - try list.append(1); - try list.append(2); - try list.append(3); + var map = std.AutoHashMap(u8, u8).init(allocator); + try map.put('a', 'b'); + try map.put('c', 'd'); + try map.put('e', 'f'); - const response = try allocator.alloc(u32, list.items.len); - @memcpy(response, list.items); - return response; + var keyIterator = map.keyIterator(); + const length = keyIterator.len; + var counted_length: usize = 0; + while (keyIterator.next()) |_| { + counted_length += 1; + } + print("DEBUG - length is {} and counted_length is {}\n", .{ length, counted_length }); + try expect(length == counted_length); } pub fn main() !void { @@ -42,5 +43,3 @@ pub fn main() !void { fn doIt(string: *const [3:0]u8) *const [9:0]u8 { return "prefix" ++ string; } - -const expect = @import("std").testing.expect; diff --git a/solutions/08.zig b/solutions/08.zig new file mode 100644 index 0000000..164e183 --- /dev/null +++ b/solutions/08.zig @@ -0,0 +1,134 @@ +const std = @import("std"); +const print = std.debug.print; +const util = @import("util.zig"); + +pub fn main() !void { + const response = try part_one(false); + print("{}\n", .{response}); +} + +const Point = struct { x: usize, y: usize }; + +fn part_one(is_test_case: bool) !usize { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const input_file = try util.getInputFile("08", is_test_case); + const data = try util.readAllInputWithAllocator(input_file, allocator); + defer allocator.free(data); + print("DEBUG - created data", .{}); + + // In this problem I'm experimenting with not even parsing the input into lines, but just keeping a "line counter" + // that is incremented whenever we hit a `\n` character + var width: ?usize = null; + var height: ?usize = null; + var x: usize = 0; + var y: usize = 0; + var antennae = std.AutoHashMap(u8, std.ArrayList(Point)).init(allocator); + defer antennae.deinit(); + + for (data) |c| { + print("DEBUG - checking {c} at {}, {}\n", .{ c, x, y }); + switch (c) { + '\n' => { + if (width == null) { + width = x; + } + x = 0; + y += 1; + }, + '.' => { + x += 1; + }, + else => { + const point = Point{ .x = x, .y = y }; + // https://ziggit.dev/t/problem-with-hashmaps/7221 was helpful! + var result = try antennae.getOrPut(c); + if (!result.found_existing) { + result.value_ptr.* = std.ArrayList(Point).init(allocator); + } + try result.value_ptr.append(point); + x += 1; + }, + } + } + height = y; // No `+1` because the trailing newline will do that for us. + print("DEBUG - height is {} and width is {}\n", .{ height.?, width.? }); + + // Zig lacks a Set (https://github.com/ziglang/zig/issues/6919), so we abuse a HashMap to pretend + var all_antinodes = std.AutoHashMap(Point, usize).init(allocator); + defer all_antinodes.deinit(); + + var it = antennae.valueIterator(); + while (it.next()) |v| { + const node_pairs = try pairs(v.items, allocator); + defer allocator.free(node_pairs); + for (node_pairs) |node_pair| { + const antinodes = try findAntinodes(node_pair, width.?, height.?, allocator); + defer allocator.free(antinodes); + for (antinodes) |antinode| { + try all_antinodes.put(antinode, 1); // We don't actually need to put any value - just populating the key + } + } + // allocator.free(v); <-- because we can't do this, there will still always be memory leaked :'( + } + + var count: usize = 0; + var antinode_iterator = all_antinodes.keyIterator(); + print("DEBUG - antinodes are:\n", .{}); + while (antinode_iterator.next()) |antinode| { + print("{}\n", .{antinode}); + count += 1; + } + return count; +} + +fn pairs(nodes: []Point, allocator: std.mem.Allocator) ![][2]Point { + var output = std.ArrayList([2]Point).init(allocator); + defer output.deinit(); + var a: usize = 0; + while (a < nodes.len - 1) : (a += 1) { + var b = a + 1; + while (b < nodes.len) : (b += 1) { + try output.append(.{ nodes[a], nodes[b] }); + } + } + return output.toOwnedSlice(); +} + +fn findAntinodes(nodes: [2]Point, width: usize, height: usize, allocator: std.mem.Allocator) ![]Point { + var response = std.ArrayList(Point).init(allocator); + defer response.deinit(); + + if (2 * nodes[1].x >= nodes[0].x and 2 * nodes[1].y >= nodes[0].y) { + const antiNode1 = Point{ .x = 2 * nodes[1].x - nodes[0].x, .y = 2 * nodes[1].y - nodes[0].y }; + if (antiNodeIsValid(antiNode1, width, height)) { + try response.append(antiNode1); + } + } + + if (2 * nodes[0].x >= nodes[1].x and 2 * nodes[0].y >= nodes[1].y) { + const antiNode2 = Point{ .x = 2 * nodes[0].x - nodes[1].x, .y = 2 * nodes[0].y - nodes[1].y }; + if (antiNodeIsValid(antiNode2, width, height)) { + try response.append(antiNode2); + } + } + + return response.toOwnedSlice(); +} + +fn antiNodeIsValid(antiNode: Point, width: usize, height: usize) bool { + // Don't technically need to check for >= 0 because that's already checked in `findAntinodes` (because otherwise + // there would be integer overflow by daring to use a negative number :P ), but doesn't hurt to replicate it here - + // otherwise a future reader might think we've forgotten it. + return antiNode.x >= 0 and antiNode.y >= 0 and antiNode.x < width and antiNode.y < height; +} + +const expect = std.testing.expect; + +test "part_one" { + const part_one_response = try part_one(true); + print("DEBUG - part_one_response is {}\n", .{part_one_response}); + try expect(part_one_response == 14); +}