I decided to try out Zig to do day 1.
Setup
First we import the standard library and initiate an allocator. In main, I have a switch statement to switch between the example input and the real input. Then, we parse the input, and print out the results of solving part 1 and part 2 of the problem.
const std = @import("std");
const mem = std.mem;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const ArrayList = std.ArrayList;
pub fn main() void {
const input = switch (true) {
true => @embedFile("input.txt"),
false =>
\\3 4
\\4 3
\\2 5
\\1 3
\\3 9
\\3 3
,
};
const parsed = parseInput(input);
defer parsed[0].deinit();
defer parsed[1].deinit();
std.debug.print("Part 1: {d}\n", .{part1(parsed)});
std.debug.print("Part 2: {d}\n", .{part2(parsed)});
}
Parsing
The input represents two lists of numbers and is in the format of multiple lines, where each line has the 2 numbers, one for each list.
3 4
4 3
2 5
1 3
3 9
3 3
To parse this, I split into lines using the splitScalar function. Then I iterate over each line, split each line on spaces. I used different functions for splitting the line as there are multiple spaces between the two numbers on each line and the tokenizeScalar function treats muliple consecutive delimiters as a single delimiter. Then, for each part of the line, I parse the string into an integer and push it into an ArrayList
. I chose u24 as my integer type as the numbers in the actual input weren’t very large. Then, the function returns an array of 2 ArrayLists.
fn parseInput(input: []const u8) [2]ArrayList(u24) {
const splitScalar = std.mem.splitScalar;
const trim = std.mem.trim;
const tokenizeScalar = std.mem.tokenizeScalar;
var it = splitScalar(u8, trim(u8, input, "\n"), '\n');
var nums1 = ArrayList(u24).init(allocator);
var nums2 = ArrayList(u24).init(allocator);
while (it.next()) |line| {
var nums_it = tokenizeScalar(u8, line, ' ');
nums1.append(parseInt(nums_it.next().?)) catch unreachable;
nums2.append(parseInt(nums_it.next().?)) catch unreachable;
}
return .{ nums1, nums2 };
}
// Helper function wrapping std.fmt.parseInt
fn parseInt(s: []const u8) u24 {
return std.fmt.parseInt(u24, s, 10) catch unreachable;
}
Part One
We need to pair up the smallest number in the first list with the smallest number in the second list, the second-smallest in the first list with the second smallest in the second list, and so on. This basically means that we have sort both lists in ascending order. We then need to find out the absolute value of the difference between each such pair. To do this, I first converted the unsigned integers to signed integers, as the difference could be negative. It is also interesting that Zig zips arrays for you if you pass two arrays to the for block.
fn part1(input: [2]ArrayList(u24)) u32 {
const sort = std.mem.sort;
const nums1 = input[0];
const nums2 = input[1];
sort(u24, nums1.items, {}, std.sort.asc(u24));
sort(u24, nums2.items, {}, std.sort.asc(u24));
var ans: u32 = 0;
for (nums1.items, nums2.items) |a, b| {
ans += @abs(@as(i25, a) - @as(i25, b));
}
return ans;
}
Part Two
For this part, we need to find the number of times an item in the first list appears in the second list. We then need to multiply the first number with that count. The answer is the sum of all these products.
To find out the counts, we use a hash map, with each number in the second list as the key and its count as the value.
fn part2(input: [2]ArrayList(u24)) u32 {
const nums1 = input[0];
const nums2 = input[1];
var counts = std.AutoHashMap(u24, u32).init(allocator);
defer counts.deinit();
for (nums2.items) |a| {
if (counts.get(a)) |count| {
counts.put(a, count + 1) catch unreachable;
} else {
counts.put(a, 1) catch unreachable;
}
}
var ans: u32 = 0;
for (nums1.items) |a| {
const count = counts.get(a) orelse 0;
ans += a * count;
}
return ans;
}