From aa408ad75ba11154ce4e3e9805caa14a6cc972fd Mon Sep 17 00:00:00 2001 From: Jack Jackson Date: Thu, 19 Dec 2024 14:32:12 -0800 Subject: [PATCH] Solution to 1-2 --- NOTES.md | 41 +++++++++++++++++++++++++++++++ README.md | 8 ++++++ main.zig | 48 ++++++++++++++++++++++++++++++++++++ scratch.zig | 18 ++++++++++++++ solutions/01.zig | 64 ++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 main.zig create mode 100644 scratch.zig diff --git a/NOTES.md b/NOTES.md index d62d5aa..154bd43 100644 --- a/NOTES.md +++ b/NOTES.md @@ -3,6 +3,8 @@ Notes, thoughts, or questions that arose as I implemented the solutions. Hopeful # Things I like * [Continue expressions](https://zig-by-example.com/while) +* Built in optionals (with `orelse`) +* Better error-handling than GoLang's (though that bar is set _real_ low). I have only just scratched the surface, though, it looks interestingly powerful - might well be even better than I've realized at this point! # Things that I've found missing from this language @@ -10,6 +12,7 @@ I mean, Jesus Christ, right now it seems even worse than GoLang. * [String concatenation](https://old.reddit.com/r/Zig/comments/bfcsul/concatenating_zig_strings/) * [String equality](https://nofmal.github.io/zig-with-example/string-handling/#string-equal) +* Switching on strings # Questions @@ -83,3 +86,41 @@ test { I can fix it by changing the type signature to accept `[]const u8`, but (I think?) that then means that I can't call the function with non-const-length strings - including strings read from files. [This](https://stackoverflow.com/questions/72736997/how-to-pass-a-c-string-into-a-zig-function-expecting-a-zig-string) link refers to `[]const u8` as a "Zig-style string-slice", but also refers to `[*c]const u8` as a "c_string", so...:shrug:? + +## Why can't I iterate over a HashMap? + +The following code: + +```zig +const std = @import("std"); +const print = std.debug.print; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + var hashMap = std.AutoHashMap(u32, u32).init(allocator); + + try hashMap.put(2, 5); + try hashMap.put(1, 35); + try hashMap.put(4, 20); + + const iter = hashMap.keyIterator(); + while (try iter.next()) |key| { + print("{}\n", .{key}); + } +} +``` + +gives: + +``` +scratch.zig:15:20: error: expected type '*hash_map.HashMapUnmanaged(u32,u32,hash_map.AutoContext(u32),80).FieldIterator(u32)', found '*const hash_map.HashMapUnmanaged(u32,u32,hash_map.AutoContext(u32),80).FieldIterator(u32)' + while (try iter.next()) |key| { + ~~~~^~~~~ +scratch.zig:15:20: note: cast discards const qualifier +/Users/scubbo/zig/zig-macos-x86_64-0.14.0-dev.2362+a47aa9dd9/lib/std/hash_map.zig:894:35: note: parameter type declared here + pub fn next(self: *@This()) ?*T { +``` + +I _think_ this means that the pointer to the Iterator is a Const-pointer and `.next()` expects a mutable pointer. But, if so - how do we get a mutable pointer from a const? I tried `@ptrCast` but that gave a similar error. diff --git a/README.md b/README.md index d9f7c34..397580c 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,11 @@ If you - like me - are new to the Zig language, [Ziglings](https://codeberg.org/ziglings/exercises/) seems to be a well-respected entrypoint! The authoritative source for this code is on [Gitea](https://gitea.scubbo.org/scubbo/advent-of-code-2024). The GitHub version is a [mirror](https://docs.gitea.com/usage/repo-mirror#setting-up-a-push-mirror-from-gitea-to-github). [Self-hosting](https://old.reddit.com/r/selfhosted/) is not only the best way to learn, but also to reduce dependency on untrustworthy corporations. + +# Execution + +I've tried (in `main.zig`) to make a general-purpose executable that can be passed arguments to determine the function to run (e.g. `zig run main.zig -- 1 2`), but so far no luck - lots of type errors 🙃 + +So for now, run directly with (e.g.) `zig run solutions/01.zig`, and do the following manual changes: +* Change `pub fn main() void {...}` in each solution-file to invoke the function you want run. +* Change `isTestCase` from `true` to `false` when ready to get the real solution. \ No newline at end of file diff --git a/main.zig b/main.zig new file mode 100644 index 0000000..31c1d40 --- /dev/null +++ b/main.zig @@ -0,0 +1,48 @@ +const std = @import("std"); + +// Unfortunately Zig doesn't appear to support dynamic imports - that is, non-literal arguments to `@import` - +// so we can't do `const module = @import(util.concatString(&.{"solutions/", problem_number, ".zig"}))` + +const problem_one = @import("solutions/01.zig"); + +pub fn main() void { + // std.debug.print("There are {d} args:\n", .{std.os.argv.len}); + // for (std.os.argv) |arg| { + // std.debug.print(" {s}\n", .{arg}); + // } + const args = std.os.argv; + const problem_number = parse_input_as_number(args[1]); + const sub_problem_number = parse_input_as_number(args[2]); + const module = switch (problem_number) { + 1 => { + return problem_one; + }, + else => { + unreachable; + }, + }; + switch (sub_problem_number) { + 1 => { + module.part_one(); + }, + 2 => { + module.part_two(); + }, + else => { + unreachable; + }, + } +} + +fn parse_input_as_number(input: [*:0]u8) i32 { + // We _should_ be able to just use `try std.fmt.parseInt(i32, input)` - but that complains with + // `expected type '[]const u8', found '[*:0]u8'` + // and I CBA to figure out how to do that conversion when I have the time pressure of solving problems! + // That can be for later learning :P + var output_value: i32 = 0; + for (input) |char| { + output_value *= 10; + output_value + char; + } + return output_value; +} diff --git a/scratch.zig b/scratch.zig new file mode 100644 index 0000000..e460099 --- /dev/null +++ b/scratch.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const print = std.debug.print; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + var hashMap = std.AutoHashMap(u32, u32).init(allocator); + + try hashMap.put(2, 5); + try hashMap.put(1, 35); + try hashMap.put(4, 20); + + const iter = hashMap.keyIterator(); + while (try iter.next()) |key| { + print("{}\n", .{key}); + } +} \ No newline at end of file diff --git a/solutions/01.zig b/solutions/01.zig index 0f5a24c..5f4ee6b 100644 --- a/solutions/01.zig +++ b/solutions/01.zig @@ -2,9 +2,13 @@ const std = @import("std"); const print = std.debug.print; const util = @import("util.zig"); +pub fn main() !void { + const output = try part_two(); + print("{}\n", .{output}); +} + pub fn part_one() !u32 { const arrays = try parseInputFileToArrays(); - std.debug.print("{any}", .{arrays.first}); const arr1 = arrays.first; const arr2 = arrays.second; @@ -30,7 +34,57 @@ pub fn part_one() !u32 { } pub fn part_two() !u32 { - return 0; + const arrays = try parseInputFileToArrays(); + const arr1 = arrays.first; + const arr2 = arrays.second; + print("DEBUG - arr1 is {any}\narr2 is {any}\n", .{arr1, arr2}); + // Logic: + // * Iterate over each array + // * Count the number of occurrences + // * For each number, increment the sum by (number * count_1 * count_2) + // (Would technically be parallelizable if you wanted to get fancy, but JFC I'm not ready for that in Zig yet) + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var counts1 = std.AutoHashMap(u32, u32).init(allocator); + var counts2 = std.AutoHashMap(u32, u32).init(allocator); + // Later, we'll need to iterate over the keys of `counts1` to sum up the final value. + // You'd think you could do this with: + // ``` + // const iter = counts1.keyIterator(); + // while (try iter.next()) |key| { + // ... + // ``` + // But that gives an error that the iterator is a const pointer but `while` requires a mutable pointer. + // So instead I preserve the encountered keys in this array so that we can then loop over them. + // Ridiculous language. + var preservedKeys = std.ArrayList(u32).init(allocator); + for (arr1) |elem| { + if (!counts1.contains(elem)) { + try preservedKeys.append(elem); + print("Added {} to the preservedKeys list\n", .{elem}); + } + try counts1.put(elem, (counts1.get(elem) orelse 0) + 1); + + } + for (arr2) |elem| { + try counts2.put(elem, (counts2.get(elem) orelse 0) + 1); + } + + print("Beginning the calculation", .{}); + var output: u32 = 0; + for (preservedKeys.items) |key| { + print("Operating on {} - at this point the output value is {}\n", .{key, output}); + const count1 = (counts1.get(key) orelse 0); + const count2 = (counts2.get(key) orelse 0); + const increment = key * count1 * count2; + print("The increment for key {} is {} (calculated based on counts {} and {})\n", .{key, increment, count1, count2}); + output += increment; + } + return output; + } fn parseInputFileToArrays() !struct { first: []u32, second: []u32 } { @@ -118,6 +172,6 @@ test "part one" { try expect(try part_one() == 11); } -// test "part two" { -// try expect(try part_two() == 31); -// } +test "part two" { + try expect(try part_two() == 31); +}