Advent of Code 2022 Day 1
The programming language
I am trying to participate in this years Advent of Code and implemented the excercise of day 1. Since doing this with a programming language I am actually halfway proficiently with, would be kinda boring, I did it with Zig.
The first problem was to get an actual compiler for the thing, since Zig is new enough to not be present in Debian. So, building from source it was (everybody can download a binary). This worked pretty straight forward and should look like this if the dependencies are present:
git clone https://github.com/ziglang/zig.git
cd zig
git checkout 0.10.0
mkdir build
cd build
cmake ..
make install
At first I omitted the install
part of the make
command in the assumption that zig would try
to install itself somewhere in my system as most make install
do, but it actually is necessary
for stage 3 of the build process (building zig with zig afaik).
The executable of the compiler should then be available at stage3/bin/zig
(relative to the build
directory).
The task
The day 1 excercise is not that complex in itself, but it was tricky to work out how to do it in Zig. You can find the whole code here if there is interest.
One of the biggest problem was to find out how to actually print anything. Copying the
const std = @import("std");
pub fn main() !void {
std.debug.print();
}
part wasn’t that hard, but finding out, that print
always expects a struct or tupel was.
So, hello world
has to look a little bit like this.
const std = @import("std");
pub fn main() !void {
std.debug.print("{s}\n", .{"Hello World"});
}
See the .{
and }
around the string? That’s a Anonymous Struct Literal.
Apart from that, dereferencing pointers has a weird syntax ( variable.*
??? ) and I did not
research why exactly that is. Error handling is also a bit uncommon (for me at least), for
the moment the lines about which the compiler complains are prefix with try
which makes the compiler
happy. Variable (or constant) declaration is quite strict, Zig will not suffer the least doubt about
what type or value a variable has (although there is undefined
for the value?) and that’s nice.
No surprises from the previous value in that memory cell.
No weird automagic conversions.
But let’s start with the code:
const std = @import("std");
const print = @import("std").debug.print;
const allocator = std.heap.page_allocator;
The first two lines are imports and one can already the very strict behaviour of zig, the imports
are assigned to identifiers and not just made available. The @
syntax marks compile time (comptime
)
functionality, meaning Zig code execute during compilation. No preprocessor her, just one language
instead of two.
The third line gives us an allocator which we need later (and yes, there are different memory allocators).
const payload = @embedFile("input.txt");
This line insert the actual excercise problem. The file is read during compilation and inserted in the
binary as a byte array. Similar to Go’s embed
or something the like coming in C23.
pub fn main() !void {
main()
function declaration, nothing surprising apart from the return type, which I do not understand
yet :-|
const stdout = std.io.getStdOut().writer();
var start: u64 = 0;
var end: u64 = 0;
var biggest_number: u64 = 0;
var tmp_number: u64 = 0;
var last_char: u8 = '0';
var sums = std.ArrayList(u64).init(allocator);
Some variable and constant definitions, types are inferred or have to be provided. In the
last line we actually use the aforementioned allocator for the ArrayList
. Btw. Zig will
heavily complain if types or values are not provided.
defer sums.deinit();
Another feature: defer
- the statement is executet at the end of the current block. Quite handy
for cleaning up, just insert the cleanup right after allocation, to be sure to not forget about it.
for (payload.*) |elem , i| {
if ((payload.*[i] >= '0') and (payload.*[i] <= '9')) {
// we got a number, so progress end
end = i;
} else if (payload.*[i] == '\n') {
// got a newline
if (last_char == '\n') {
// last char was also newline so this elf is finished
// compare current number to biggest
try sums.append(tmp_number);
if (tmp_number > biggest_number) {
biggest_number = tmp_number;
//try stdout.print("debug biggest number: {d}\n", .{biggest_number});
}
// reset number
tmp_number = 0;
// start is set to next char
start = i + 1;
} else {
// just the next number
// Parse number now
const tmp: u64 = try std.fmt.parseUnsigned(u64, payload.*[start..end+1], 10);
tmp_number += tmp;
//try stdout.print("debug sum: {d}\n", .{tmp_number});
start = i + 1;
end = start;
}
}
last_char = elem;
}
This part iterates over the input, splits at new line and detects the double newline which separates the elfs. So basically, an ugly scanner. Logically not surprising, but hard to do not for the language noob.
try stdout.print("Biggest number: {d}\n", .{biggest_number});
var x = sums.toOwnedSlice();
std.sort.sort(u64, x, {}, comptime std.sort.desc(u64));
const top_three_cal = x[0] + x[1] + x[2];
try stdout.print("Top three calories: {d}\n", .{top_three_cal});
This is just preparing and printing the result, difficult here was find a way to get a slice of my list of sums for the indiviual elf, so it could be put into the sort function.
Conclusion
Interesting first try with Zig. Just scratching the surface here, but so far it has the a nice “feeling”. The whole “C interoperability/replacement” was not even mentioned here, but might be worth a look. So currently I am thinking whether to continue with Zig tomorrow or trying something else.
Links
- Zig standard library documentation
- Zig language documentation
- Zig by example
- Zig syntax highlighting for VIM
- https://ziglearn.org