aoc-2024/day9.zig

329 lines
8.6 KiB
Zig
Raw Normal View History

2024-12-09 16:20:46 +01:00
const std = @import("std");
const utils = @import("lib/utils.zig");
const spice = @import("lib/spice.zig");
const filename = "inputs/day9.txt";
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-10 15:26:12 +01:00
try std.io.getStdOut().writer().print("Day 9, part 1: {}\n", .{result});
2024-12-09 16:20:46 +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-10 15:26:12 +01:00
try std.io.getStdOut().writer().print("Day 9, part 2: {}\n", .{result});
2024-12-09 16:20:46 +01:00
}
}
fn part1(alloc: std.mem.Allocator, reader: anytype) !u64 {
const FsEntry = enum(u32) {
empty = std.math.maxInt(u32),
_,
};
const input = try reader.readAllAlloc(alloc, std.math.maxInt(usize));
defer alloc.free(input);
var filesystem_list = std.ArrayList(FsEntry).init(alloc);
defer filesystem_list.deinit();
var next_id: u16 = 0;
var is_gap = false;
for (input) |byte| {
if (byte == '\n') {
break;
}
std.debug.assert('0' <= byte and byte <= '9');
const len: usize = @as(usize, byte - '0');
if (is_gap) {
try filesystem_list.appendNTimes(.empty, len);
} else {
try filesystem_list.appendNTimes(@enumFromInt(next_id), len);
next_id += 1;
std.debug.assert(next_id != @intFromEnum(FsEntry.empty));
}
is_gap = !is_gap;
}
const filesystem = filesystem_list.items;
// for (filesystem) |x| {
// switch (x) {
// .empty => std.debug.print(".", .{}),
// else => std.debug.print("{}", .{@intFromEnum(x)}),
// }
// }
// std.debug.print("\n", .{});
var start_idx: usize = 0;
var end_idx: usize = filesystem.len - 1;
while (start_idx < end_idx) {
// for start_idx: seek next empty position
if (filesystem[start_idx] != .empty) {
start_idx += 1;
continue;
}
// for end_idx: seek next full position
if (filesystem[end_idx] == .empty) {
end_idx -= 1;
continue;
}
std.debug.assert(filesystem[start_idx] == .empty and filesystem[end_idx] != .empty);
filesystem[start_idx] = filesystem[end_idx];
filesystem[end_idx] = .empty;
}
const first_empty_idx = std.mem.indexOfScalar(FsEntry, filesystem, .empty).?;
for (filesystem[first_empty_idx..]) |id| {
std.debug.assert(id == .empty);
}
var checksum: u64 = 0;
for (0.., filesystem) |idx, id| {
if (id == .empty) {
break;
}
checksum += @as(u64, idx) * @intFromEnum(id);
}
return checksum;
}
fn part2(alloc: std.mem.Allocator, reader: anytype) !u64 {
const FsId = enum(u16) {
empty = std.math.maxInt(u16),
_,
};
const FsEntry = struct {
len: u16,
id: FsId,
};
const input = try reader.readAllAlloc(alloc, std.math.maxInt(usize));
defer alloc.free(input);
var filesystem = std.ArrayList(FsEntry).init(alloc);
defer filesystem.deinit();
var max_id: u16 = undefined;
{
var next_id: u16 = 0;
var is_gap = false;
for (input) |byte| {
if (byte == '\n') {
break;
}
std.debug.assert('0' <= byte and byte <= '9');
const len: u16 = @as(u16, byte - '0');
if (is_gap) {
try filesystem.append(.{ .len = len, .id = .empty });
} else {
try filesystem.append(.{ .len = len, .id = @enumFromInt(next_id) });
next_id += 1;
std.debug.assert(next_id != @intFromEnum(FsId.empty));
}
is_gap = !is_gap;
}
max_id = next_id - 1;
}
// for (filesystem.items) |entry| {
// for (0..entry.len) |_| {
// switch (entry.id) {
// .empty => std.debug.print(".", .{}),
// else => std.debug.print("{}", .{@intFromEnum(entry.id)}),
// }
// }
// }
// std.debug.print("\n", .{});
{
// next ID to check; neccessary for checking all files once
// since file entries are sorted in increasing order, we can simply decrease this with each
// checked file
var next_id = max_id;
// index of next filesystem entry to check
var idx: usize = filesystem.items.len - 1;
while (idx > 0) : (idx -= 1) {
const entry = filesystem.items[idx];
// we should never see and ID smaller than the one we're looking for
// if we do that means we would skip that file
std.debug.assert(entry.id == .empty or @intFromEnum(entry.id) >= next_id);
if (@intFromEnum(entry.id) != next_id) {
continue;
}
// found next entry to move
// decrease next ID to find
next_id -= 1;
const len = entry.len;
for (0..idx) |jdx| {
const target_entry = filesystem.items[jdx];
// find the first entry with is empty and at least as large as our entry
if (target_entry.id != .empty or target_entry.len < len) {
continue;
}
// found target entry
if (target_entry.len == entry.len) {
// exact hit! no need to split entry, just swap IDs
std.mem.swap(FsId, &filesystem.items[idx].id, &filesystem.items[jdx].id);
} else {
// empty space is larger than our entry; need to split
// warning! this (potentially) invalidates all pointers into filesystem.items
// and we need to adjust indices (i.e. increase idx by one) since everything after
// jdx shift back by one index
_ = try filesystem.addManyAt(jdx + 1, 1);
idx += 1;
// jdx + 1 becomes empty entry of len target_entry.len - entry.len
filesystem.items[jdx + 1].id = .empty;
filesystem.items[jdx + 1].len = target_entry.len - entry.len;
// jdx becomes entry
filesystem.items[jdx].len = entry.len;
filesystem.items[jdx].id = entry.id;
// idx becomes empty
filesystem.items[idx].id = .empty;
}
break;
}
}
}
// for (filesystem.items) |entry| {
// for (0..entry.len) |_| {
// switch (entry.id) {
// .empty => std.debug.print(".", .{}),
// else => std.debug.print("{}", .{@intFromEnum(entry.id)}),
// }
// }
// }
// std.debug.print("\n", .{});
var checksum: u64 = 0;
{
var idx: usize = 0;
for (filesystem.items) |entry| {
const len = entry.len;
// skip over empty entry
if (entry.id == .empty) {
idx += len;
continue;
}
const id = @intFromEnum(entry.id);
for (0..len) |_| {
checksum += id * idx;
idx += 1;
}
}
}
return checksum;
}
test "part1 example" {
const alloc = std.testing.allocator;
const example = "2333133121414131402";
var stream = std.io.fixedBufferStream(example);
const result = try part1(alloc, stream.reader());
try std.testing.expectEqual(1928, result);
}
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());
try std.testing.expectEqual(6307275788409, result);
}
test "part2 example" {
const alloc = std.testing.allocator;
const example = "2333133121414131402";
var stream = std.io.fixedBufferStream(example);
const result = try part2(alloc, stream.reader());
try std.testing.expectEqual(2858, result);
}
test "part2 input" {
const alloc = std.testing.allocator;
const file_reader = try utils.FileReader.init(alloc, filename);
defer file_reader.deinit();
const result = try part2(alloc, file_reader.reader());
try std.testing.expectEqual(6327174563252, result);
}