Finn Zink

Advent of Code 2023 in Zig

Here are my solutions for the first 10 days of Advent of Code 2023:

Day 1

Here’s the code I used:

const std = @import("std");

const number_strings: [9][]const u8 = [_][]const u8{ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };

pub fn getInt(input: []const u8, index: usize) ?usize {
    for (1.., number_strings) |num, str| {
        if (index + str.len <= input.len and std.mem.eql(u8, str, input[index .. index + str.len])) {
            return num;
        }
    }
    return null;
}

pub fn main() !void {
    const data = @embedFile("./day1.txt");
    var lines = std.mem.tokenize(u8, data, "\n");
    var running_sum: u32 = 0;
    while (lines.next()) |line| {
        var first: ?usize = null;
        var last: ?usize = null;
        for (line, 0..) |char, i| {
            const isInt: ?usize = getInt(line, i);

            if (std.ascii.isDigit(char)) {
                std.debug.print("{c}", .{char});
                if (first == null) {
                    first = char - '0';
                }
                last = char - '0';
            } else if (isInt != null) {
                std.debug.print("[{}]", .{isInt.?});
                if (first == null) {
                    first = isInt;
                }
                last = isInt;
            }
        }
        if (first == null or last == null) {
            return error.MissingDigit;
        }
        const curr_val: usize = 10 * first.? + last.?;
        std.debug.print(", curr_val: {}\n", .{curr_val});
        running_sum += curr_val;
    }
    std.debug.print("{}\n", .{running_sum});
}

Day 2

The code I used is below, much of it is lifted from yesterday. Today’s solution solves both part 1 and part 2 of the problem in one pass, whereas yesterday’s was refactored for part 2 after I got the answer for part 1.

const std = @import("std");

const color_strings: [3][]const u8 = [_][]const u8{ "red", "green", "blue" };
const max_colors: [3]u8 = [_]u8{ 12, 13, 14 };

pub fn getColor(input: []const u8, index: usize) ?usize {
    for (0.., color_strings) |num, str| {
        if (index + str.len <= input.len and std.mem.eql(u8, str, input[index .. index + str.len])) {
            return num;
        }
    }
    return null;
}

pub fn main() !void {
    const data = @embedFile("./day2.txt");
    var lines = std.mem.tokenize(u8, data, "\n");
    var line_num: usize = 1;
    var game_sum: usize = 0;
    var power_sum: usize = 0;
    while (lines.next()) |line| {
        var in_game = true;
        var curr_num: u8 = 0;
        var is_valid_game = true;

        var min_vals: [3]usize = [_]usize{ 0, 0, 0 };

        for (line, 0..) |char, i| {
            const isColor: ?usize = getColor(line, i);
            if (in_game == true) {
                if (char == ':') {
                    in_game = false;
                }
            } else if (std.ascii.isDigit(char)) {
                curr_num = curr_num * 10 + char - '0';
            } else if (getColor(line, i) != null) {
                if (curr_num > max_colors[isColor.?]) {
                    is_valid_game = false;
                }
                min_vals[isColor.?] = @max(min_vals[isColor.?], curr_num);
                curr_num = 0;
            }
        }

        if (is_valid_game) {
            game_sum += line_num;
        }
        std.debug.print("min_red: {}, min_green: {}, min_blue: {}\n", .{ min_vals[0], min_vals[1], min_vals[2] });
        power_sum += min_vals[0] * min_vals[1] * min_vals[2];
        line_num += 1;
    }
    std.debug.print("Part 1: {d} (sum of valid game numbers)\n", .{game_sum});
    std.debug.print("Part 2: {d} (sum of min products of game objects)\n", .{power_sum});
}

Day 3

The problem structure didn’t lend itself well to being all in one file today, so I did them in separate files.

Here’s the first part:

const std = @import("std");

fn isSymbol(val: u8) bool {
    return !std.ascii.isDigit(val) and val != '.';
}

fn getSymbol(line: ?[]const u8, index: usize) bool {
    if (line == null) {
        return false;
    }
    var has_symbol = false;
    if (index > 0) {
        has_symbol = has_symbol or isSymbol(line.?[index - 1]);
    }
    if (index < line.?.len - 1) {
        has_symbol = has_symbol or isSymbol(line.?[index + 1]);
    }
    has_symbol = has_symbol or isSymbol(line.?[index]);
    return has_symbol;
}

pub fn main() !void {
    const data = @embedFile("./day3.txt");
    
    var lines = std.ArrayList([]const u8).init(std.heap.page_allocator);
    defer lines.deinit();

    var tokenizer = std.mem.tokenize(u8, data, "\n");
    while (tokenizer.next()) |line| {
        try lines.append(line);
    }
    var running_sum: u64 = 0;

    for (lines.items, 0..) |line, i| {
        const prev_line = if (i > 0) lines.items[i - 1] else null;
        const next_line = if (i < lines.items.len - 1) lines.items[i + 1] else null;

        var in_num = false;
        var has_symbol = false;
        var curr_num: u16 = 0;

        for (line, 0..) |char, j| {
            const is_num = std.ascii.isDigit(char);

            if (is_num and has_symbol == false) {
                has_symbol = getSymbol(prev_line, j) or getSymbol(line, j) or getSymbol(next_line, j);
            }

            if (in_num == false and is_num) { // reached start of num
                in_num = true;
                curr_num = char - '0';
            } else if (is_num) { // middle of num
                curr_num = curr_num * 10 + char - '0';
            }
            if (j == line.len - 1 or (in_num and is_num == false)) { // end of num
                if (has_symbol) {
                    std.debug.print("{d} ", .{curr_num});
                    running_sum += curr_num;
                }
                curr_num = 0;
                has_symbol = false;
                in_num = false;
            }
        }
        std.debug.print("\n", .{});
    }
    std.debug.print("Part 1: {d} (sum of all numbers surrounded by a symbol)\n", .{running_sum});
}

And here’s the second part, which got a bit messy:

const Symbol = struct {
    symbol: u8,
    index: usize,
};

const std = @import("std");

fn getSymbol(line: ?[]const u8, index: usize) ?Symbol {
    if (line == null) {
        return null;
    }
    if (index > 0 and line.?[index - 1] == '*') {
        return Symbol{ .symbol = '*', .index = index - 1 };
    }
    if (index < line.?.len - 1 and line.?[index + 1] == '*') {
        return Symbol{ .symbol = '*', .index = index + 1 };
    }
    if (line.?[index] == '*') {
        return Symbol{ .symbol = '*', .index = index };
    }

    return null;
}

pub fn main() !void {
    const data = @embedFile("./day3.txt");

    var lines = std.ArrayList([]const u8).init(std.heap.page_allocator);
    defer lines.deinit();

    var gear_hash = std.AutoArrayHashMap([2]usize, [2]usize).init(std.heap.page_allocator);
    defer gear_hash.deinit();

    var tokenizer = std.mem.tokenize(u8, data, "\n");
    while (tokenizer.next()) |line| {
        try lines.append(line);
    }

    for (lines.items, 0..) |line, i| {
        const prev_line = if (i > 0) lines.items[i - 1] else null;
        const next_line = if (i < lines.items.len - 1) lines.items[i + 1] else null;

        var in_num = false;
        var has_symbol: ?u8 = null;
        var sym_i: usize = 0;
        var sym_j: usize = 0;
        var curr_num: u16 = 0;

        for (line, 0..) |char, j| {
            const is_num = std.ascii.isDigit(char);

            if (is_num and has_symbol == null) {
                const prev_sym = getSymbol(prev_line, j);
                if (prev_sym != null and (prev_sym.?.symbol == '*' or has_symbol == null)) {
                    has_symbol = prev_sym.?.symbol;
                    sym_i = i - 1;
                    sym_j = prev_sym.?.index;
                }
                const curr_sym = getSymbol(line, j);
                if (curr_sym != null and (curr_sym.?.symbol == '*' or has_symbol == null)) {
                    has_symbol = curr_sym.?.symbol;
                    sym_i = i;
                    sym_j = curr_sym.?.index;
                }
                const next_sym = getSymbol(next_line, j);
                if (next_sym != null and (next_sym.?.symbol == '*' or has_symbol == null)) {
                    has_symbol = next_sym.?.symbol;
                    sym_i = i + 1;
                    sym_j = next_sym.?.index;
                }
            }

            if (in_num == false and is_num) { // reached start of num
                in_num = true;
                curr_num = char - '0';
            } else if (is_num) { // middle of num
                curr_num = curr_num * 10 + char - '0';
            }
            if (j == line.len - 1 or (in_num and is_num == false)) { // end of num
                if (has_symbol == '*') {
                    const key = [2]usize{ sym_i, sym_j };
                    const ptr = gear_hash.get(key);
                    if (ptr) |val| {
                        try gear_hash.put(key, [2]usize{ val[0] + 1, val[1] * curr_num });
                        std.debug.print("Updated key {d}, {d}: {d}, {d}\n", .{ sym_i, sym_j, val[0] + 1, val[1] * curr_num });
                    } else {
                        try gear_hash.put(key, [2]usize{ 1, curr_num });
                        std.debug.print("Inserted key {d}, {d}: 1, {d}\n", .{ sym_i, sym_j, curr_num });
                    }
                }
                curr_num = 0;
                has_symbol = null;
                in_num = false;
            }
        }
        std.debug.print("\n", .{});
    }
    var running_sum: usize = 0;
    var it = gear_hash.iterator();
    while (it.next()) |entry| {
        // const key_ptr = entry.key_ptr;
        const value_ptr = entry.value_ptr;

        if (value_ptr.*[0] == 2) {
            running_sum += value_ptr.*[1];
        }
    }
    std.debug.print("Part 2: {d} (sum of all gear ratios)\n", .{running_sum});
}

Day 4

Happier with the code today, but maybe the problem was just a little easier.

const ThreeState = enum {
    inTitle,
    inWinning,
    inNumbers,
};

const std = @import("std");

pub fn main() !void {
    const data = @embedFile("./day4.txt");
    var lines = std.mem.tokenize(u8, data, "\n");
    var total_prize: usize = 0;
    var total_cards: usize = 0;
    var index: usize = 0;
    var card_counts: [188]usize = [_]usize{1} ** 188;
    while (lines.next()) |line| {
        var state = ThreeState.inTitle;
        var curr_num: usize = 0;
        var winning_nums: [10]usize = undefined;
        var winning_nums_size: usize = 0;
        var curr_prize: usize = 0;
        var num_matching: usize = 0;
        for (line, 0..) |char, i| {
            if (char == ':') {
                state = ThreeState.inWinning;
                curr_num = 0;
            } else if (char == '|') {
                state = ThreeState.inNumbers;
            } else if (char == ' ' or i == line.len - 1) {
                if (i == line.len - 1) {
                    curr_num = curr_num * 10 + char - '0';
                }
                if (curr_num != 0) {
                    if (state == ThreeState.inWinning) {
                        winning_nums[winning_nums_size] = curr_num;
                        winning_nums_size += 1;
                    } else if (state == ThreeState.inNumbers) {
                        for (winning_nums) |winning_num| {
                            if (winning_num == curr_num) {
                                if (curr_prize == 0) {
                                    curr_prize = 1;
                                } else {
                                    curr_prize *= 2;
                                }
                                num_matching += 1;
                            }
                        }
                    }
                }
                curr_num = 0;
            } else if (std.ascii.isDigit(char)) {
                curr_num = curr_num * 10 + char - '0';
            }
        }
        for (0..num_matching) |i| {
            if (index + i + 1 < 188) {
                card_counts[index + i + 1] += card_counts[index];
            }
        }
        std.debug.print("index: {d} | num_matching: {d} | card_counts[index]: {d}\n", .{index, num_matching, card_counts[index]});
        total_prize += curr_prize;
        total_cards += card_counts[index];
        index += 1;
        
    }
    std.debug.print("Part 1: {d}\n", .{total_prize});
    std.debug.print("Part 2: {d}\n", .{total_cards});
}

Day 5

Ran out of time for part 2 [Edit: I came back and solved it on the 6th], spent a ton of time on part 1. Tricky one today.

const std = @import("std");
fn compare(_: void, a: [3]u64, b: [3]u64) bool {
    return a[1] < b[1];
}

pub fn main() !void {
    const data = @embedFile("./day5.txt");
    const allocator = std.heap.page_allocator;
    var chunks = std.mem.tokenize(u8, data, ":");

    var seeds = std.ArrayList(u64).init(allocator);
    defer seeds.deinit();

    _ = chunks.next();

    if (chunks.next()) |firstChunk| {
        const pos = std.mem.indexOf(u8, firstChunk, "\n");
        const newChunk = firstChunk[0..pos.?];
        var seedTokens = std.mem.tokenize(u8, newChunk, " ");
        // _ = seedTokens.next();
        while (seedTokens.next()) |token| {
            const seed = try std.fmt.parseInt(u64, token, 10);
            try seeds.append(seed);
        }
    }

    var mapping = std.ArrayList([3]u64).init(allocator);
    defer mapping.deinit();
    while (chunks.next()) |chunk| {
        // std.debug.print("======", .{});
        // std.debug.print("{s}", .{chunk});
        // std.debug.print("======", .{});
        var lines = std.mem.tokenize(u8, chunk, "\n");
        while (lines.next()) |line| {
            if (!std.ascii.isDigit(line[0])) {
                break;
            }
            var tokens = std.mem.tokenize(u8, line, " ");
            var curr_map: [3]u64 = undefined;
            var i: usize = 0;
            while (tokens.next()) |token| {
                const val = try std.fmt.parseInt(u64, token, 10);
                curr_map[i] = val;
                i += 1;
            }
            try mapping.append(curr_map);
        } 
    
        std.debug.print("mappings: ", .{});
        for (mapping.items) |item| {
            std.debug.print("[{d},{d},{d}] ", .{ item[0], item[1], item[2] });
        }
        std.debug.print("\n", .{});

        std.mem.sort([3]u64, mapping.items, {}, compare);
        std.mem.sort(u64, seeds.items, {}, comptime std.sort.asc(u64));

        var ptr1: usize = 0;
        var ptr2: usize = 0;

        while (ptr1 < seeds.items.len and ptr2 < mapping.items.len) {
            const to = mapping.items[ptr2][0];
            const from = mapping.items[ptr2][1];
            const range = mapping.items[ptr2][2];

            if (seeds.items[ptr1] >= from and seeds.items[ptr1] <= from + range - 1) {
                std.debug.print("{d}->{d} range: {d}-{d} [{d}, {d}, {d}] \n", .{ seeds.items[ptr1], to + (seeds.items[ptr1] - from), from, from + range - 1, to, from, range });
                seeds.items[ptr1] = to + (seeds.items[ptr1] - from);
                ptr1 += 1;
                continue;
            }

            if (seeds.items[ptr1] > from + range - 1) {
                ptr2 += 1;
            } else {
                ptr1 += 1;
            }
        }
        mapping.clearAndFree();
        std.debug.print("seeds: ", .{});
        for (seeds.items) |item| {
            std.debug.print("{d} ", .{item});
        }

        std.debug.print("\n\n", .{});
        continue;
    }
    std.debug.print("Part 1: {d}\n", .{std.mem.min(u64, seeds.items)});
}

Here’s the part 2 code that ended up solving it. The line “else if (seed[0] != 0)” at the end is hacky and it definitely indicates a bug, but I can’t find it…

const std = @import("std");
fn compare(_: void, a: [3]u64, b: [3]u64) bool {
    return a[1] < b[1];
}

pub fn main() !void {
    const data = @embedFile("./day5.txt");
    const allocator = std.heap.page_allocator;
    var chunks = std.mem.tokenize(u8, data, ":");

    var seeds = std.ArrayList([2]i64).init(allocator);
    defer seeds.deinit();

    _ = chunks.next();

    if (chunks.next()) |firstChunk| {
        const pos = std.mem.indexOf(u8, firstChunk, "\n");
        const newChunk = firstChunk[0..pos.?];
        var seedTokens = std.mem.tokenize(u8, newChunk, " ");
        // _ = seedTokens.next();
        var first: ?i64 = null;
        while (seedTokens.next()) |token| {
            const seed = try std.fmt.parseInt(i64, token, 10);
            if (first == null) {
                first = seed;
            } else {
                try seeds.append([_]i64{ first.?, seed });
                first = null;
            }
        }
    }

    std.debug.print("\nstarting seeds: ", .{});
    for (seeds.items) |item| {
        std.debug.print("[{d}-{d}] ", .{ item[0], item[0] + item[1] - 1 });
    }

    std.debug.print("\n", .{});

    var mapping = std.ArrayList([3]i64).init(allocator);
    defer mapping.deinit();
    while (chunks.next()) |chunk| {
        // std.debug.print("======", .{});
        // std.debug.print("{s}", .{chunk});
        // std.debug.print("======", .{});
        var lines = std.mem.tokenize(u8, chunk, "\n");
        while (lines.next()) |line| {
            if (!std.ascii.isDigit(line[0])) {
                break;
            }
            var tokens = std.mem.tokenize(u8, line, " ");
            var curr_map: [3]i64 = undefined;
            var i: usize = 0;
            while (tokens.next()) |token| {
                const val = try std.fmt.parseInt(i64, token, 10);
                curr_map[i] = val;
                i += 1;
            }
            try mapping.append(curr_map);
        }

        std.mem.sort([3]i64, mapping.items, {}, compare);

        std.debug.print("mappings: ", .{});
        for (mapping.items) |item| {
            std.debug.print("[{d}-{d}]->[{d}-{d}] ", .{ item[1], item[1] + item[2] - 1, item[0], item[0] + item[2] - 1 });
        }
        std.debug.print("\n", .{});

        var new_seeds = std.ArrayList([2]i64).init(allocator);
        defer new_seeds.deinit();

        for (seeds.items) |seed| {
            var seed_start = seed[0];
            const seed_end = seed[0] + seed[1] - 1;
            var mapping_found = false;
            for (mapping.items) |map| {
                const map_start = map[1];
                const map_end = map[1] + map[2] - 1;
                const map_dist = map[0] - map[1];

                if (seed_start >= map_start and seed_end <= map_end) {
                    try new_seeds.append([_]i64{ seed_start + map_dist, seed[1] });
                    std.debug.print("normal map: [{}-{}]->[{}-{}]\n", .{ seed_start, seed_end, seed_start + map_dist, seed_end + map_dist });
                    mapping_found = true;
                } else if (seed_start >= map_start and seed_start <= map_end) {
                    std.debug.print("BREAK (start) for seed [{}-{}] and map [{}-{}]\n", .{ seed_start, seed_end, map_start, map_end });
                    try new_seeds.append([_]i64{ seed_start + map_dist, map_end - seed_start + 1 });
                    std.debug.print("BREAK (start) adding {},{}:\n", .{ seed_start + map_dist, map_end - seed_start + 1 });
                    seed_start = map_end + 1;
                } else if (seed_end >= map_start and seed_end <= map_end) {
                    std.debug.print("BREAK (end) for seed [{}-{}] and map [{}-{}]\n", .{ seed_start, seed_end, map_start, map_end });
                    try new_seeds.append([_]i64{ seed_start, map_start - seed_start });
                    std.debug.print("BREAK (end) adding {},{}:\n", .{ seed_start, map_start - seed_start });
                    try new_seeds.append([_]i64{ map_start + map_dist, seed_end - map_start + 1 });
                    std.debug.print("BREAK (end) adding {},{}:\n", .{ map_start + map_dist, seed_end - map_start + 1 });
                    mapping_found = true;
                }
            }
            if (!mapping_found) {
                try new_seeds.append([_]i64{ seed_start, seed_end - seed_start + 1 });
            }
        }
        seeds.clearAndFree();
        for (new_seeds.items) |seed| {
            try seeds.append(seed);
        }
        mapping.clearAndFree();
        std.debug.print("\nnext seeds: ", .{});
        for (seeds.items) |item| {
            std.debug.print("[{d}-{d}] ", .{ item[0], item[0] + item[1] - 1 });
        }

        std.debug.print("\n", .{});
        // break;
    }
    var minny: i64 = std.math.maxInt(i64);
    for (seeds.items) |seed| {
        if (minny == undefined) {
            minny = seed[0];
        } else if (seed[0] != 0){
            minny = @min(minny, seed[0]);
        }
        std.debug.print("minny: {}; seed={},{}\n", .{minny, seed[0], seed[1]});
    }
    std.debug.print("Part 2: {d}\n", .{minny});
}

Day 6

The Algebra was a nice surprise. I also split the logic for this one into a separate function so I could utilize zig test.

const std = @import("std");

fn calculateWinnings(times: []const f64, distances: []const f64) i32 {
    var total: i32 = 1;
    for (times, distances) |time, distance| {
        const root1 = (-1 * time + @sqrt(time * time - 4 * distance)) / -2;
        const root2 = (-1 * time - @sqrt(time * time - 4 * distance)) / -2;
        
        var winnings = @floor(root2) - @ceil(root1) + 1;
        if (@mod(root2, 1) == 0) {
            winnings -= 1;
        }
        if (@mod(root1, 1) == 0) {
            winnings -= 1;
        }
        total *= @intFromFloat(winnings);
    }
    return total;
}

pub fn main() void {
    const times: [4]f64 = [_]f64{ 46, 68, 98, 66 };
    const distances: [4]f64 = [_]f64{ 358, 1054, 1807, 1080 };

    const total = calculateWinnings(times[0..], distances[0..]);
    std.debug.print("\nPart 1: {}\n", .{total});

    const singleTime: [1]f64 = [_]f64{46689866};
    const singleDistance: [1]f64 = [_]f64{358105418071080};

    const total2 = calculateWinnings(singleTime[0..], singleDistance[0..]);
    std.debug.print("\nPart 2: {}\n",.{total2});
}

test "test calculateWinnings" {
    const times: [3]f64 = [_]f64{ 7, 15, 30 };
    const distances: [3]f64 = [_]f64{ 9, 40, 200 };
    try std.testing.expect(288 == calculateWinnings(times[0..], distances[0..]));
}

Day 7

Code for part 1 and part 2 are below:

const std = @import("std");

const Hand = enum(u8) {
    HighCard = 1,
    OnePair = 2,
    TwoPair = 3,
    ThreeOfAKind = 4,
    FullHouse = 5,
    FourOfAKind = 6,
    FiveOfAKind = 7,
};

const Element = struct {
    hand: [5]u8,
    bid: usize,
    value: Hand,
};

fn getRank(a: u8) u8 {
    if (a - '0' > 0 and a - '0' <= 9) {
        return a - '0';
    }
    return switch(a) {
        'T' => 10,
        'J' => 11,
        'Q' => 12,
        'K' => 13,
        'A' => 14,
        else => 0,
    };
}

fn compare(_: void, a: Element, b: Element) bool {
    if (a.value != b.value) {
        return @intFromEnum(a.value) < @intFromEnum(b.value);
    }
    for (0..5) |i| {
        if (a.hand[i] != b.hand[i]) {
            return getRank(a.hand[i]) < getRank(b.hand[i]); 
        }
    }
    return false;
}

pub fn main() !void {
    const data = @embedFile("./day7.txt");
    var lines = std.mem.tokenize(u8, data, "\n");

    var hands: [1000]Element = undefined;

    var i : usize = 0;
    while (lines.next()) |line| {
        var hand: [5]u8 = undefined;
        std.mem.copy(u8, hand[0..5], line[0..5]);

        // dist is basically 
        var dist = [_]usize{1,0,0,0,0};
        var j: usize = 4;
        outer: while (j > 0) : (j -= 1) {
            for (0..j) |k| {
                if (hand[j] == hand[k]) {
                    dist[k] += 1;
                    continue :outer;
                }
            }
            dist[j] += 1;
        }
        std.mem.sort(usize, &dist, {}, comptime std.sort.desc(usize));

        var value: Hand = undefined;
        if (dist[0] == 5) {
            value = Hand.FiveOfAKind;
        } else if (dist[0] == 4) {
            value = Hand.FourOfAKind;
        } else if (dist[0] == 3 and dist[1] == 2) {
            value = Hand.FullHouse;
        } else if (dist[0] == 3) {
            value = Hand.ThreeOfAKind;
        } else if (dist[0] == 2 and dist[1] == 2) {
            value = Hand.TwoPair;
        } else if (dist[0] == 2) {
            value = Hand.OnePair;
        } else {
            value = Hand.HighCard;
        }

        hands[i] = Element{.hand = hand, .bid = try std.fmt.parseInt(u32, line[6..], 10), .value = value};
        i += 1;
    }

    std.mem.sort(Element, &hands, {}, compare);

    var runningTotal : usize = 0;
    for (hands, 1..) |hand, index| {
        runningTotal += (hand.bid * index);
        std.debug.print("{s} {d}: {d}\n", .{hand.hand, hand.bid, runningTotal});
    }
    std.debug.print("Part 1: {d}\n", .{runningTotal});
}</code></pre>
<p>Part 2:</p>
    <pre><code class="language-zig">const std = @import("std");

const Hand = enum(u8) {
    HighCard = 1,
    OnePair = 2,
    TwoPair = 3,
    ThreeOfAKind = 4,
    FullHouse = 5,
    FourOfAKind = 6,
    FiveOfAKind = 7,
};

const Element = struct {
    hand: [5]u8,
    bid: usize,
    value: Hand,
};

fn getRank(a: u8) u8 {
    if (a - '0' > 0 and a - '0' <= 9) {
        return a - '0';
    }
    return switch(a) {
        'T' => 10,
        'J' => 0,
        'Q' => 12,
        'K' => 13,
        'A' => 14,
        else => 0,
    };
}

fn compare(_: void, a: Element, b: Element) bool {
    if (a.value != b.value) {
        return @intFromEnum(a.value) < @intFromEnum(b.value);
    }
    for (0..5) |i| {
        if (a.hand[i] != b.hand[i]) {
            return getRank(a.hand[i]) < getRank(b.hand[i]); 
        }
    }
    return false;
}

pub fn main() !void {
    const data = @embedFile("./day7.txt");
    var lines = std.mem.tokenize(u8, data, "\n");

    var hands: [1000]Element = undefined;

    var i : usize = 0;
    while (lines.next()) |line| {
        var hand: [5]u8 = undefined;
        std.mem.copy(u8, hand[0..5], line[0..5]);

        var dist = [_]usize{0,0,0,0,0};
        var j: usize = 5;
        var jokers : usize = 0;
        outer: while (j > 0) : (j -= 1) {
            if (hand[j-1] == 'J') {
                jokers += 1;
                continue :outer;
            }
            for (0..j-1) |k| {
                if (hand[j-1] == hand[k]) {
                    dist[k] += 1;
                    continue :outer;
                }
            }
            dist[j-1] += 1;
        }
        std.mem.sort(usize, &dist, {}, comptime std.sort.desc(usize));
        dist[0] += jokers;
        std.debug.print("{s}: [{d},{d},{d},{d},{d}]\n", .{hand, dist[0], dist[1], dist[2], dist[3], dist[4]});

        var value: Hand = undefined;
        if (dist[0] == 5) {
            value = Hand.FiveOfAKind;
        } else if (dist[0] == 4) {
            value = Hand.FourOfAKind;
        } else if (dist[0] == 3 and dist[1] == 2) {
            value = Hand.FullHouse;
        } else if (dist[0] == 3) {
            value = Hand.ThreeOfAKind;
        } else if (dist[0] == 2 and dist[1] == 2) {
            value = Hand.TwoPair;
        } else if (dist[0] == 2) {
            value = Hand.OnePair;
        } else {
            value = Hand.HighCard;
        }

        hands[i] = Element{.hand = hand, .bid = try std.fmt.parseInt(u32, line[6..], 10), .value = value};
        i += 1;
    }

    std.mem.sort(Element, &hands, {}, compare);

    var runningTotal : usize = 0;
    for (hands, 1..) |hand, index| {
        runningTotal += (hand.bid * index);
        std.debug.print("{s} {d}: {d}\n", .{hand.hand, hand.bid, runningTotal});
    }
    std.debug.print("Part 2: {d}\n", .{runningTotal});
}

Day 8

Fun problem, esp part 2. Code below:

const std = @import("std");

pub fn main() !void {
    const data = @embedFile("./day8.txt");
    var lines = std.mem.tokenize(u8, data, "\n");

    const allocator = std.heap.page_allocator;
    var steps = std.ArrayList(bool).init(allocator);
    defer steps.deinit();

    if (lines.next()) |line| {
        for (line) |char| {
            if (char == 'L') {
                try steps.append(false);
            } else if (char == 'R') {
                try steps.append(true);
            }
        }
    }
    var map = std.StringHashMap([2][3]u8).init(allocator);
    defer map.deinit();

    while (lines.next()) |line| {
        var left_str: [3]u8 = undefined;
        var right_str: [3]u8 = undefined;

        std.mem.copy(u8, &left_str, line[7..10]);
        std.mem.copy(u8, &right_str, line[12..15]);

        std.debug.print("adding: {s}: {s},{s}\n", .{line[0..3], left_str, right_str});

        try map.put(line[0..3], .{ left_str, right_str });
    }
    var key: [3]u8 = .{'A', 'A', 'A'};
    var index: usize = 0;
    while (!std.mem.eql(u8, &key, "ZZZ")) {
        const curr_val = map.get(&key);
        if (curr_val == null) {
            return error.KeyNotFound;
        }
        if (steps.items[index % steps.items.len] == false) {
            std.mem.copy(u8, &key, &curr_val.?[0]);
        } else {
            std.mem.copy(u8, &key, &curr_val.?[1]);
        }
        index += 1;
    }
    std.debug.print("Part 1: {}\n", .{index});
}

Part 2:

const std = @import("std");

pub fn gcd(a: u128, b: u128) u128 {
    var temp_a = a;
    var temp_b = b;
    while (temp_b != 0) {
        const temp = temp_a % temp_b;
        temp_a = temp_b;
        temp_b = temp;
    }
    return temp_a;
}

pub fn main() !void {
    const data = @embedFile("./day8.txt");
    var lines = std.mem.tokenize(u8, data, "\n");

    const allocator = std.heap.page_allocator;
    var steps = std.ArrayList(bool).init(allocator);
    defer steps.deinit();

    if (lines.next()) |line| {
        for (line) |char| {
            if (char == 'L') {
                try steps.append(false);
            } else if (char == 'R') {
                try steps.append(true);
            }
        }
    }
    var map = std.StringHashMap([2][3]u8).init(allocator);
    defer map.deinit();
    var start_keys = std.ArrayList([3]u8).init(allocator);
    defer start_keys.deinit();

    while (lines.next()) |line| {
        var left_str: [3]u8 = undefined;
        var right_str: [3]u8 = undefined;

        std.mem.copy(u8, &left_str, line[7..10]);
        std.mem.copy(u8, &right_str, line[12..15]);

        if (line[2] == 'A') {
            try start_keys.append(line[0..3].*);
            // std.debug.print("adding: {s}: {s},{s}\n", .{line[0..3], left_str, right_str});
        }
        try map.put(line[0..3], .{ left_str, right_str });
    }
    
    var num_steps = std.ArrayList(u128).init(allocator);
    defer num_steps.deinit();

    for (start_keys.items, 0..) |key, i| { 
        _ = key;
        var index: usize = 0;
        while (start_keys.items[i][2] != 'Z') {
            const curr_val = map.get(&start_keys.items[i]);
            if (curr_val == null) {
                return error.KeyNotFound;
            }
            if (steps.items[index % steps.items.len] == false) {
                // std.debug.print("{s}->{s}\n", .{start_keys.items[i], curr_val.?[0]});
                start_keys.items[i] = curr_val.?[0];
            } else {
                // std.debug.print("{s}->{s}\n", .{start_keys.items[i], curr_val.?[1]});
                start_keys.items[i] = curr_val.?[1];
            }
            index += 1;
        }
        std.debug.print("{d}\n", .{index});
        try num_steps.append(index);
    }

    std.debug.print("\n", .{});

    var total_steps : u128 = num_steps.items[0];
    for (1..num_steps.items.len) |i| {
        std.debug.print("{d}\n", .{total_steps});
        total_steps = (total_steps * num_steps.items[i]) / gcd(total_steps, num_steps.items[i]);
    }
    std.debug.print("\nPart 2: {d}\n", .{total_steps});
}

Day 9

// naive way is just arraylist of arraylists and solve it like the problem suggests
const std = @import("std");

pub fn main() !void {
    const data = @embedFile("./day9.txt");
    var lines = std.mem.tokenize(u8, data, "\n");

    const allocator = std.heap.page_allocator;

    var end_total : i64 = 0;
    var start_total : i64 = 0;

    while (lines.next()) |line| {
        var pyramid = std.ArrayList(std.ArrayList(i64)).init(allocator);
        defer pyramid.deinit();

        var firstRow = std.ArrayList(i64).init(allocator);

        var tokens = std.mem.tokenize(u8, line, " ");
        while (tokens.next()) |token| {
            const num = try std.fmt.parseInt(i64, token, 10);
            try firstRow.append(num);
        }
        try pyramid.append(firstRow);

        var pyramid_index: usize = 0;
        var is_zeros = false;
        while (!is_zeros) {
            is_zeros = true;
            var row = std.ArrayList(i64).init(allocator);
            const length = pyramid.items[pyramid_index].items.len;
            for (1..length) |i| {
                const val = pyramid.items[pyramid_index].items[i] - pyramid.items[pyramid_index].items[i-1];
                if (val != 0) {
                    is_zeros = false;
                }
                try row.append(val);
            }

            end_total += pyramid.items[pyramid_index].items[length - 1];
            if (pyramid_index % 2 == 0) {
                start_total += pyramid.items[pyramid_index].items[0];
            } else {
                start_total -= pyramid.items[pyramid_index].items[0];
            }

            try pyramid.append(row);
            pyramid_index += 1;
        }

        for (pyramid.items) |pyramid_line| {
            for (pyramid_line.items) |pyramid_item| {
                std.debug.print("{d} ", .{pyramid_item});
            }
            pyramid_line.deinit();
            std.debug.print("\n", .{});
        }
        
    }
    std.debug.print("Part 1: {}\n", .{end_total});
    std.debug.print("Part 2: {}\n", .{start_total});
}

Day 10

X╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭---╮
X|╰╯||||||||||||╭--╯
X╰-╮╰╯╰╯||||||╰╯╰-╮X
╭--╯╭--╮||╰╯╰╯0╭╮╭╯X
╰---╯╭-╯╰╯0000╭╯╰╯XX
XXX╭-╯╭---╮000╰╮XXXX
XX╭╯╭╮╰╮╭-╯╭╮00╰---╮
XX╰-╯╰╮||╭╮|╰╮╭-╮╭╮|
XXXXX╭╯|||||╭╯╰╮||╰╯
XXXXX╰-╯╰╯╰╯╰--╯╰╯XX
const std = @import("std");
pub fn main() !void {
    const data = @embedFile("./day10.txt");
    const allocator = std.heap.page_allocator;
    var data_copy = try allocator.alloc(u8, data.len);
    std.mem.copy(u8, data_copy, data);

    var lines = std.mem.tokenize(u8, data, "\n");
    const length = lines.next().?.len + 1; // +1 is for newlines
    var s_slice = std.mem.tokenize(u8, data, "S");
    const s_len = s_slice.next().?.len;

    var prev_pos = s_len + 1;
    var curr_pos = s_len;

    if (curr_pos >= length and curr_pos - length != prev_pos // north
    and (data[curr_pos - length] == '7' or data[curr_pos - length] == 'F' or data[curr_pos - length] == '|')) {
        prev_pos = curr_pos;
        curr_pos -= length;
    } else if (curr_pos + length < data.len and curr_pos + length != prev_pos // south
    and (data[curr_pos + length] == 'L' or data[curr_pos + length] == 'J' or data[curr_pos + length] == '|')) {
        prev_pos = curr_pos;
        curr_pos += length;
    } else if (curr_pos + 1 < data.len and data[curr_pos + 1] != '\n' and curr_pos + 1 != prev_pos // east
    and (data[curr_pos + 1] == '-' or data[curr_pos + 1] == 'J' or data[curr_pos + 1] == '7')) {
        prev_pos = curr_pos;
        curr_pos += 1;
    } else { // west
        prev_pos = curr_pos;
        curr_pos -= 1;
    }
    var runningSum: usize = 1;
    while (data[curr_pos] != 'S') {
        // data_copy[curr_pos] = '*';
        // std.debug.print("processing: <{c}> ", .{data[curr_pos]});
        const temp_pos = curr_pos;
        switch (data[curr_pos]) {
            '|' => {
                data_copy[curr_pos] = '5';
                curr_pos = if (curr_pos == prev_pos + length) curr_pos + length else curr_pos - length;
            },
            '-' => {
                data_copy[curr_pos] = '6';
                curr_pos = if (curr_pos == prev_pos + 1) curr_pos + 1 else curr_pos - 1;
            },
            'J' => {
                data_copy[curr_pos] = '4';
                curr_pos = if (curr_pos == prev_pos + 1) curr_pos - length else curr_pos - 1;
            },
            'F' => {
                data_copy[curr_pos] = '1';
                curr_pos = if (curr_pos == prev_pos - 1) curr_pos + length else curr_pos + 1;
            },
            'L' => {
                data_copy[curr_pos] = '3';
                curr_pos = if (curr_pos == prev_pos + length) curr_pos + 1 else curr_pos - length;
            },
            '7' => {
                data_copy[curr_pos] = '2';
                curr_pos = if (curr_pos == prev_pos + 1) curr_pos + length else curr_pos - 1;
            },
            else => curr_pos = curr_pos,
        }
        prev_pos = temp_pos;
        // std.debug.print("[curr:{} prev:{}]\n", .{ curr_pos, prev_pos });
        runningSum += 1;
    }
    data_copy[s_len] = '5'; // this is set manually based on my puzzle input, to lazy to generalize it :D
    std.debug.print("PART 1: {}\n", .{runningSum/2});
    
    var top_count: usize = 0;
    var bottom_count: usize = 0;
    var count: usize = 0;

    var barrier_count: usize= 0;
    for (data_copy) |char| {
        switch(char) {
            '1' => {
                std.debug.print("╭", .{});
                bottom_count = 1;
            },
            '2' => {
                std.debug.print("╮", .{});
                if (top_count == 1) {
                    barrier_count += 1;
                    top_count = 0;
                } else if (bottom_count == 1) {
                    bottom_count = 0;
                } else {
                    @panic("YIKES1");
                }
            },
            '3' => {
                std.debug.print("╰", .{});
                top_count += 1;
            },
            '4' => {
                std.debug.print("╯", .{});
                if (bottom_count == 1) {
                    barrier_count += 1;
                    bottom_count = 0;
                } else if (top_count == 1) {
                    top_count = 0;
                } else {
                    @panic("YIKES2");
                }
            },
            '5' => {
                std.debug.print("|", .{});
                barrier_count += 1;
            },
            '6' => {
                std.debug.print("-", .{});
            },
            '\n' => {
                std.debug.print("\n", .{});
                if (top_count != 0 or bottom_count != 0) {
                    @panic("YIKES3");
                }
                barrier_count = 0;
            },
            else => {
                if (barrier_count % 2 == 0) {
                    std.debug.print("X", .{});
                } else {
                    std.debug.print("0", .{});
                    count += 1;
                }
            }
        }
    }
    std.debug.print("\nPART 2: {}\n", .{count});

}