aoc-2024/day5.zig

450 lines
12 KiB
Zig
Raw Normal View History

2024-12-06 02:06:14 +01:00
const std = @import("std");
2024-12-07 23:27:28 +01:00
const utils = @import("lib/utils.zig");
2024-12-06 02:06:14 +01:00
const List = std.DoublyLinkedList(u8);
2024-12-11 22:22:04 +01:00
const day = "5";
const filename = "inputs/day" ++ day ++ ".txt";
2024-12-07 23:27:28 +01:00
2024-12-06 02:06:14 +01:00
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
{
const file_reader = try utils.FileReader.init(alloc, filename);
defer file_reader.deinit();
const result = try part1(alloc, file_reader.reader());
2024-12-11 22:22:04 +01:00
try std.io.getStdOut().writer().print("Day " ++ day ++ ", part 1: {}\n", .{result});
2024-12-06 02:06:14 +01:00
}
{
const file_reader = try utils.FileReader.init(alloc, filename);
defer file_reader.deinit();
const result = try part2(alloc, file_reader.reader());
2024-12-11 22:22:04 +01:00
try std.io.getStdOut().writer().print("Day " ++ day ++ ", part 2: {}\n", .{result});
2024-12-06 02:06:14 +01:00
}
}
2024-12-06 15:35:57 +01:00
fn checkUndefined(comptime T: type, ptr: *const T) bool {
const bytes = std.mem.asBytes(ptr);
for (bytes) |byte| {
if (byte != 0xaa) {
return false;
}
}
return true;
}
2024-12-06 02:06:14 +01:00
const Edge = struct { from: u8, to: u8 };
fn isCorrectlyOrdered(pages: []const u8, edges: std.AutoHashMap(Edge, void)) bool {
for (0..pages.len) |i| {
for (i + 1..pages.len) |j| {
const edge = Edge{ .from = pages[j], .to = pages[i] };
if (edges.contains(edge)) {
return false;
}
}
}
return true;
}
fn findIndex(slice: []const u8, value: u8) ?usize {
for (0.., slice) |i, x| {
if (x == value) {
return i;
}
}
return null;
}
fn findNode(list: List, value: u8) ?*List.Node {
var node_ptr_maybe = list.first;
while (node_ptr_maybe != null) : (node_ptr_maybe = node_ptr_maybe.?.next) {
const node_ptr = node_ptr_maybe.?;
if (node_ptr.*.data == value) {
return node_ptr;
}
}
return null;
}
2024-12-06 15:35:57 +01:00
fn topo_sort(alloc: std.mem.Allocator, elems: []u8, edges: std.AutoHashMap(Edge, void)) !void {
// first build maps of edges
var outgoing_edges = std.AutoHashMap(u8, std.AutoHashMap(u8, void)).init(alloc);
var incoming_edges = std.AutoHashMap(u8, std.AutoHashMap(u8, void)).init(alloc);
{
var it = edges.iterator();
while (it.next()) |edge| {
const from = edge.key_ptr.from;
const to = edge.key_ptr.to;
// if either to or from are not in elems, ignore this edge
// -> not in subgraph spanned by elems
if (findIndex(elems, from) == null or findIndex(elems, to) == null) {
continue;
}
// add outgoing edge to outgoing_edges[from][to]
const outgoing_entry = try outgoing_edges.getOrPut(from);
if (!outgoing_entry.found_existing) {
// check for undefined
std.debug.assert(checkUndefined(@TypeOf(outgoing_entry.value_ptr.*), outgoing_entry.value_ptr));
outgoing_entry.value_ptr.* = std.AutoHashMap(u8, void).init(alloc);
}
// check for undefined
std.debug.assert(!checkUndefined(@TypeOf(outgoing_entry.value_ptr.*), outgoing_entry.value_ptr));
try outgoing_entry.value_ptr.put(to, {});
// add incoming edge to incoming_edges[to][from]
const incoming_entry = try incoming_edges.getOrPut(to);
if (!incoming_entry.found_existing) {
std.debug.assert(checkUndefined(@TypeOf(incoming_entry.value_ptr.*), incoming_entry.value_ptr));
incoming_entry.value_ptr.* = std.AutoHashMap(u8, void).init(alloc);
}
std.debug.assert(!checkUndefined(@TypeOf(incoming_entry.value_ptr.*), incoming_entry.value_ptr));
try incoming_entry.value_ptr.put(from, {});
}
}
{
var it = edges.iterator();
while (it.next()) |edge| {
const from = edge.key_ptr.from;
const to = edge.key_ptr.to;
// if either to or from are not in elems, ignore this edge
// -> not in subgraph spanned by elems
if (findIndex(elems, from) == null or findIndex(elems, to) == null) {
continue;
}
std.debug.assert(outgoing_edges.get(from).?.get(to) != null);
std.debug.assert(incoming_edges.get(to).?.get(from) != null);
}
}
2024-12-06 02:06:14 +01:00
// this function works in-place by splitting elems into an first, sorted part and a later,
// unsorted part
// this index marks the first element of the unsorted part of elems
var next_unsorted_idx: usize = 0;
// step 1: find all nodes with no incoming edges, move them to the sorted part of the list
2024-12-06 15:35:57 +01:00
for (0..elems.len) |i| {
const incoming_set = incoming_edges.get(elems[i]);
if (incoming_set == null) {
2024-12-06 02:06:14 +01:00
// if elems[i] has no incoming edges, move it to the end of the sorted list
std.mem.swap(u8, &elems[next_unsorted_idx], &elems[i]);
// advance end of sorted list by one
next_unsorted_idx += 1;
2024-12-06 15:35:57 +01:00
} else {
std.debug.assert(incoming_set.?.count() != 0);
2024-12-06 02:06:14 +01:00
}
}
// step 2: progressively iterate over sorted section, removing outgoing edges and adding nodes
// with no incoming edges to the end of the sorted section
for (elems) |value| {
2024-12-06 15:35:57 +01:00
2024-12-06 02:06:14 +01:00
// if value has outgoing edges...
2024-12-06 15:35:57 +01:00
if (outgoing_edges.get(value)) |outgoing_set| {
2024-12-06 02:06:14 +01:00
// iterate over outgoing edges
2024-12-06 15:35:57 +01:00
var outgoing_entry_it = outgoing_set.iterator();
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
while (outgoing_entry_it.next()) |entry| {
const next = entry.key_ptr.*;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
// we have an edge value -> next
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
// cannot fail: we know the edge (value, to_value) exists
const incoming_map = incoming_edges.getPtr(next).?;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
{
const found = incoming_map.remove(value);
std.debug.assert(found);
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
if (incoming_map.count() == 0) {
// no more incoming edges for next
{
const found = incoming_edges.remove(next);
std.debug.assert(found);
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
// cannot fail: to_elem must be in unsorted section of elems // [next_unsorted_idx..]
const to_value_index = next_unsorted_idx + findIndex(elems[next_unsorted_idx..], next).?;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
std.mem.swap(u8, &elems[next_unsorted_idx], &elems[to_value_index]);
next_unsorted_idx += 1;
2024-12-06 02:06:14 +01:00
}
}
// remove all outgoing edges at once
2024-12-06 15:35:57 +01:00
{
const found = outgoing_edges.remove(value);
std.debug.assert(found);
}
2024-12-06 02:06:14 +01:00
}
}
2024-12-06 15:35:57 +01:00
std.debug.assert(incoming_edges.count() == 0);
std.debug.assert(outgoing_edges.count() == 0);
2024-12-06 02:06:14 +01:00
}
fn part1(alloc: std.mem.Allocator, reader: anytype) !u64 {
var line_reader = utils.lineReader(alloc, reader);
defer line_reader.deinit();
var edges = std.AutoHashMap(Edge, void).init(alloc);
defer edges.deinit();
while (try line_reader.next()) |line| {
if (line.len == 0) {
break;
}
var it = utils.numberParserWithDelimiter(u8, line, '|');
const from = (try it.next()).?;
const to = (try it.next()).?;
std.debug.assert((try it.next()) == null);
try edges.put(.{ .from = from, .to = to }, {});
2024-12-06 15:35:57 +01:00
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
var update = std.ArrayList(u8).init(alloc);
defer update.deinit();
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
var sum_middle_pages: u64 = 0;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
while (try line_reader.next()) |line| {
update.clearRetainingCapacity();
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
var it = utils.numberParserWithDelimiter(u8, line, ',');
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
while (try it.next()) |n| {
try update.append(n);
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
std.debug.assert(update.items.len % 2 == 1);
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
if (!isCorrectlyOrdered(update.items, edges)) {
continue;
}
const middle_idx = update.items.len / 2;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
sum_middle_pages += update.items[middle_idx];
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
return sum_middle_pages;
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
fn part2(alloc: std.mem.Allocator, reader: anytype) !u64 {
var line_reader = utils.lineReader(alloc, reader);
defer line_reader.deinit();
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
var edge_arena = std.heap.ArenaAllocator.init(alloc);
defer edge_arena.deinit();
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
var edges = std.AutoHashMap(Edge, void).init(alloc);
defer edges.deinit();
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
while (try line_reader.next()) |line| {
if (line.len == 0) {
break;
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
var it = utils.numberParserWithDelimiter(u8, line, '|');
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
const from = (try it.next()).?;
const to = (try it.next()).?;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
std.debug.assert((try it.next()) == null);
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
try edges.put(.{ .from = from, .to = to }, {});
}
2024-12-06 02:06:14 +01:00
var update = std.ArrayList(u8).init(alloc);
defer update.deinit();
var sum_middle_pages: u64 = 0;
while (try line_reader.next()) |line| {
update.clearRetainingCapacity();
var it = utils.numberParserWithDelimiter(u8, line, ',');
while (try it.next()) |n| {
try update.append(n);
}
std.debug.assert(update.items.len % 2 == 1);
// var update_idx: usize = 0;
// for (elems) |n| {
// if (update.items[update_idx] == n) {
// update_idx += 1;
// }
// if (update_idx == update.items.len) {
// const middle_idx = update.items.len / 2;
// sum_middle_pages += update.items[middle_idx];
// break;
// }
// }
if (isCorrectlyOrdered(update.items, edges)) {
2024-12-06 15:35:57 +01:00
continue;
2024-12-06 02:06:14 +01:00
}
2024-12-06 15:35:57 +01:00
try topo_sort(edge_arena.allocator(), update.items, edges);
const middle_idx = update.items.len / 2;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
sum_middle_pages += update.items[middle_idx];
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
return sum_middle_pages;
2024-12-06 02:06:14 +01:00
}
test "part1 example" {
const alloc = std.testing.allocator;
const example =
\\47|53
\\97|13
\\97|61
\\97|47
\\75|29
\\61|13
\\75|53
\\29|13
\\97|29
\\53|29
\\61|53
\\97|53
\\61|29
\\47|13
\\75|47
\\97|75
\\47|61
\\75|61
\\47|29
\\75|13
\\53|13
\\
\\75,47,61,53,29
\\97,61,53,29,13
\\75,29,13
\\75,97,47,61,53
\\61,13,29
\\97,13,75,29,47
;
var stream = std.io.fixedBufferStream(example);
const result = try part1(alloc, stream.reader());
2024-12-07 23:27:28 +01:00
try std.testing.expectEqual(143, result);
2024-12-06 02:06:14 +01:00
}
test "part1 input" {
const alloc = std.testing.allocator;
const file_reader = try utils.FileReader.init(alloc, filename);
defer file_reader.deinit();
const result = try part1(alloc, file_reader.reader());
2024-12-07 23:27:28 +01:00
try std.testing.expectEqual(5129, result);
2024-12-06 02:06:14 +01:00
}
2024-12-06 15:35:57 +01:00
test "part2 example" {
const alloc = std.testing.allocator;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
const example =
\\47|53
\\97|13
\\97|61
\\97|47
\\75|29
\\61|13
\\75|53
\\29|13
\\97|29
\\53|29
\\61|53
\\97|53
\\61|29
\\47|13
\\75|47
\\97|75
\\47|61
\\75|61
\\47|29
\\75|13
\\53|13
\\
\\75,47,61,53,29
\\97,61,53,29,13
\\75,29,13
\\75,97,47,61,53
\\61,13,29
\\97,13,75,29,47
;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
var stream = std.io.fixedBufferStream(example);
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
const result = try part2(alloc, stream.reader());
2024-12-06 02:06:14 +01:00
2024-12-07 23:27:28 +01:00
try std.testing.expectEqual(123, result);
2024-12-06 15:35:57 +01:00
}
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
test "part2 input" {
const alloc = std.testing.allocator;
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
const file_reader = try utils.FileReader.init(alloc, filename);
defer file_reader.deinit();
2024-12-06 02:06:14 +01:00
2024-12-06 15:35:57 +01:00
const result = try part2(alloc, file_reader.reader());
2024-12-06 02:06:14 +01:00
2024-12-07 23:27:28 +01:00
try std.testing.expectEqual(4077, result);
2024-12-06 15:35:57 +01:00
}