Advent of Code 2021 in Zig - Day 1

Publish date: Dec 22, 2021
Tags: zig aoc

Recently on Hacker News I saw that Zig 0.9.0 was released and it rekindled my interest in this language. Besides reading the Why Zig When There is Already C++, D, and Rust? page and a few other pages from the docs I’ve never tried to use it, so instead of moving on to day 3 in Kotlin I thought I’d start again (at least for one day) in Zig. I haven’t read all the docs yet, so there’s no doubt that what I’ve come up with is not ideal.

Here’s the day 1 problem, and my previous solution in Kotlin.

The problem itself is quite straight forward, and this solves it.

const ArrayList = @import("std").ArrayList;

fn increases(depths: ArrayList(usize), window_size: usize) usize {
    var count: usize = 0;
    var i: usize = window_size;
    while (i < depths.items.len) : (i += 1) {
        if (depths.items[i] > depths.items[i - window_size]) {
            count += 1;
        }
    }
    return count;
}

That came together quite quickly, but working out how to read the input data from disk and run tests took me much longer. Zig is still in development, the API changes often, and the docs have some gaps, so most of the following came from lots of trial and error and DuckDuckGoing.

Here’s how I read data from disk:

const fs = @import("std").fs;
const io = @import("std").io;
const fmt = @import("std").fmt;
const ArrayList = @import("std").ArrayList;

pub fn readUnsigned(path: []const u8, buffer: *ArrayList(usize)) !void {
    const file = try fs.cwd().openFile(path, .{});
    defer file.close();

    var buf_reader = io.bufferedReader(file.reader());
    var in_stream = buf_reader.reader(); 

    var buf: [1024]u8 = undefined;
    while (try in_stream.readUntilDelimiterOrEof(&buf, '\n')) |line| {
        try buffer.append(try fmt.parseUnsigned(usize, line, 10));
    }
}

const testing = @import("std").testing;
const eql = @import("std").mem.eql;

test "reads data as unsigned" {
    var data = ArrayList(usize).init(testing.allocator);
    defer data.deinit();

    try readUnsigned("./src/data/day1.txt", &data);

    try testing.expectEqual(@as(usize, 2000), data.items.len);
    try testing.expectEqual(@as(usize, 198), data.items[0]);
    try testing.expectEqual(@as(usize, 10930), data.items[data.items.len - 1]);
}

The steps are:

  1. Open the file and defer closing
  2. Read into a buffer. Not needed in this case as you could read lines from file.reader(), but it’s good to have that working. Also, the Stack Overflow answer I copied it from had already done it for me.
  3. Read lines into a 1 kibibyte buffer. We know our input, for day 1 it’s just a few bytes per line and this is way more than we need, so reading this way is fine, but we’d have to just read the stream for larger inputs and handle buffering lines ourselves.

We can now test our increases function with:

const testing = @import("std").testing;
const readUnsigned = @import("read-data.zig").readUnsigned;

test "example data" {
    var depths = ArrayList(usize).init(testing.allocator);
    defer depths.deinit();
    try readUnsigned("./src/data/day1example.txt", &depths);

    try testing.expectEqual(@as(usize, 7), increases(depths, 1)); // part 1
    try testing.expectEqual(@as(usize, 5), increases(depths, 3)); // part 2
}

test "real data" {
    var depths = ArrayList(usize).init(testing.allocator);
    defer depths.deinit();
    try readUnsigned("./src/data/day1.txt", &depths);

    try testing.expectEqual(@as(usize, 1791), increases(depths, 1)); // part 1
    try testing.expectEqual(@as(usize, 1822), increases(depths, 3)); // part 2
}

The last part is building and I’m not sure that this is the right way to do it, but it works.

const std = @import("std");

pub fn build(b: *std.build.Builder) void {
    // Standard release options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
    const mode = b.standardReleaseOptions();

    const read_data_tests = b.addTest("src/read-data.zig");
    read_data_tests.setBuildMode(mode);

    const day1_tests = b.addTest("src/day1.zig");
    day1_tests.setBuildMode(mode);

    const test_step = b.step("test", "Run library tests");
    test_step.dependOn(&read_data_tests.step);
    test_step.dependOn(&day1_tests.step);
}

The project is on GitHub.


Thanks for reading! Please feel free to send me an email to talk more about this (or anything else for that matter).