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);
|
|
|
|
|
}
|