Solutions of most (39 out of 50 so far) puzzles in Zig (system language, alternative for C). My first experience with it.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

417 lines
12 KiB

const std = @import("std");
fn Order(comptime T: type) type {
return struct {
fn order(context: void, lhs: T, rhs: T) std.math.Order {
_ = context;
return std.math.order(lhs, rhs);
}
};
}
fn StackList(comptime T: type, comptime capacity_type: type, comptime capacity: capacity_type) type {
return struct {
const Self = @This();
mem: [capacity]T,
length: capacity_type,
fn add(self: *Self, value: T) void {
self.mem[self.length] = value;
self.length += 1;
}
fn addIfNotNull(self: *Self, nullable_value: ?T) void {
if (nullable_value) |value| {
self.add(value);
}
}
fn has(self: *const Self, needle: T) bool {
for (0..self.length) |i| {
if (self.mem[i] == needle) {
return true;
}
}
return false;
}
fn addUnique(self: *Self, value: T) void {
if (!self.has(value)) {
self.add(value);
}
}
fn getMutableSlice(self: *Self) []T {
return (&self.mem)[0..self.length];
}
fn getSlice(self: *const Self) []const T {
return self.mem[0..self.length];
}
fn init() Self {
return Self{
.mem = undefined,
.length = 0,
};
}
};
}
fn RingQueueWrapper(comptime T: type, comptime size: usize) type {
return struct {
const Self = @This();
mem: *[size]T,
first: usize,
next: usize,
fn add(self: *Self, value: T) void {
std.debug.assert(self.next - self.first < size);
self.mem.*[self.next % size] = value;
self.next += 1;
}
fn take(self: *Self) T {
const result = self.mem.*[self.first % size];
self.first += 1;
return result;
}
fn isEmpty(self: *const Self) bool {
return self.next == self.first;
}
fn init(mem: *[size]T) Self {
return Self{
.mem = mem,
.first = 0,
.next = 0,
};
}
};
}
fn readNumber(comptime T: type, line: []const u8, index: *usize) T {
var result: T = 0;
while (index.* < line.len and line[index.*] == ' ') : (index.* += 1) {}
var is_negative = false;
if (index.* < line.len and line[index.*] == '-') {
is_negative = true;
index.* += 1;
}
std.debug.assert(index.* < line.len);
while (index.* < line.len) : (index.* += 1) {
const char = line[index.*];
switch (char) {
'0'...'9' => {
result = result * 10 + (char - '0');
},
else => {
break;
},
}
}
return if (is_negative) (0 - result) else result;
}
fn readHexNumber(comptime T: type, line: []const u8, index: *usize) T {
var result: T = 0;
while (index.* < line.len and line[index.*] == ' ') : (index.* += 1) {}
var is_negative = false;
if (index.* < line.len and line[index.*] == '-') {
is_negative = true;
index.* += 1;
}
std.debug.assert(index.* < line.len);
while (index.* < line.len) : (index.* += 1) {
const char = line[index.*];
switch (char) {
'0'...'9' => {
result = result * 16 + (char - '0');
},
'a'...'f' => {
result = result * 16 + 10 + (char - 'a');
},
'A'...'F' => {
result = result * 16 + 10 + (char - 'A');
},
else => {
break;
},
}
}
return if (is_negative) (0 - result) else result;
}
fn Coordinates(comptime CoordinateType: type) type {
return struct {
const Self = @This();
x: CoordinateType,
y: CoordinateType,
fn isSameX(self: *const Self, other: Self) bool {
return self.x == other.x;
}
fn approachX(self: *Self, other: Self) void {
if (self.x < other.x) {
self.x += 1;
} else {
self.x -= 1;
}
}
fn isSameY(self: *const Self, other: Self) bool {
return self.y == other.y;
}
fn approachY(self: *Self, other: Self) void {
if (self.y < other.y) {
self.y += 1;
} else {
self.y -= 1;
}
}
};
}
fn GenericTerrain(comptime CoordinateType: type, comptime height: CoordinateType, comptime width: CoordinateType, comptime CellType: type) type {
return struct {
const Self = @This();
coordinates: Coordinates(CoordinateType),
cells: [height][width]CellType,
fn initZeroes() Self {
return .{
.coordinates = .{
.x = height / 2,
.y = width / 2,
},
.cells = std.mem.zeroes([height][width]CellType),
};
}
fn getCurrentCell(self: *const Self) CellType {
return self.cells[self.coordinates.x][self.coordinates.y];
}
fn setCurrentCell(self: *Self, value: CellType) void {
self.cells[self.coordinates.x][self.coordinates.y] = value;
}
};
}
const CellStatus = enum(u2) {
Default = 0,
NotDug = 1,
Dug = 2,
};
const TerrainCoordinate = u16;
const Terrain = GenericTerrain(TerrainCoordinate, 2048, 2048, CellStatus);
const TerrainCoordinates = Coordinates(TerrainCoordinate);
const Direction = enum(u8) {
Right = 0,
Down = 1,
Left = 2,
Up = 3,
};
const RawCoordinate = u32;
const RawCommand = packed struct(u32) {
direction: Direction,
length: u24,
};
const RawCoordinates = Coordinates(RawCoordinate);
fn floodTerrain(terrain: *Terrain) void {
var queue_mem: [65_536]TerrainCoordinates = undefined;
var tasks = RingQueueWrapper(TerrainCoordinates, queue_mem.len).init(&queue_mem);
tasks.add(.{ .x = 0, .y = 0 });
while (!tasks.isEmpty()) {
const task = tasks.take();
terrain.coordinates = task;
//std.debug.print("Processing task for {d},{d} (current status: {any})\n", .{ x, y, terrain.*.cells[x][y] });
if (terrain.getCurrentCell() == .Default) {
terrain.setCurrentCell(.NotDug);
//std.debug.print("Marked {d},{d} as NotDug\n", .{ x, y });
const x = task.x;
const y = task.y;
if (x > 0) {
tasks.add(.{ .x = x - 1, .y = y });
}
if (x + 1 < terrain.*.cells.len) {
tasks.add(.{ .x = x + 1, .y = y });
}
if (y > 0) {
tasks.add(.{ .x = x, .y = y - 1 });
}
if (y + 1 < terrain.*.cells[x].len) {
tasks.add(.{ .x = x, .y = y + 1 });
}
}
}
}
fn getTargetRawCoordinates(coordinates: RawCoordinates, command: RawCommand) RawCoordinates {
return RawCoordinates{
.x = switch (command.direction) {
.Right => coordinates.x + command.length,
.Left => coordinates.x - command.length,
.Up, .Down => coordinates.x,
},
.y = switch (command.direction) {
.Down => coordinates.y + command.length,
.Up => coordinates.y - command.length,
.Left, .Right => coordinates.y,
},
};
}
fn getCoordinate(known_values: []const RawCoordinate, raw_coordinate: RawCoordinate) TerrainCoordinate {
const maybe_index = std.sort.binarySearch(RawCoordinate, raw_coordinate, known_values, {}, Order(RawCoordinate).order);
return @as(TerrainCoordinate, @intCast(maybe_index.?));
}
fn solveForCommands(raw_commands: []const RawCommand) usize {
const start_value = 1 << 23;
var raw_coordinates = RawCoordinates{
.x = start_value,
.y = start_value,
};
var known_values = StackList(RawCoordinate, u16, 2048).init();
known_values.addUnique(0);
known_values.addUnique(start_value - 1);
known_values.addUnique(start_value);
known_values.addUnique(start_value + 1);
known_values.addUnique(std.math.maxInt(RawCoordinate));
for (raw_commands) |raw_command| {
raw_coordinates = getTargetRawCoordinates(raw_coordinates, raw_command);
known_values.addUnique(raw_coordinates.x - 1);
known_values.addUnique(raw_coordinates.x);
known_values.addUnique(raw_coordinates.x + 1);
known_values.addUnique(raw_coordinates.y - 1);
known_values.addUnique(raw_coordinates.y);
known_values.addUnique(raw_coordinates.y + 1);
}
std.sort.heap(RawCoordinate, known_values.getMutableSlice(), {}, std.sort.asc(RawCoordinate));
var known_values_slice = known_values.getSlice();
raw_coordinates = RawCoordinates{
.x = start_value,
.y = start_value,
};
var terrain = Terrain.initZeroes();
terrain.coordinates = .{
.x = getCoordinate(known_values_slice, start_value),
.y = getCoordinate(known_values_slice, start_value),
};
for (raw_commands) |raw_command| {
raw_coordinates = getTargetRawCoordinates(raw_coordinates, raw_command);
const target = TerrainCoordinates{
.x = getCoordinate(known_values_slice, raw_coordinates.x),
.y = getCoordinate(known_values_slice, raw_coordinates.y),
};
while (!terrain.coordinates.isSameX(target)) {
terrain.coordinates.approachX(target);
terrain.setCurrentCell(.Dug);
}
while (!terrain.coordinates.isSameY(target)) {
terrain.coordinates.approachY(target);
terrain.setCurrentCell(.Dug);
}
}
floodTerrain(&terrain);
const cells = terrain.cells;
var result: u64 = 0;
for (0..cells.len) |x| {
for (0..cells[x].len) |y| {
if (cells[x][y] != .NotDug) {
// Doesn't really matter whether we take x..x+1 or x-1..x here, because boundary blocks will always have size 1
result += @as(u64, known_values_slice[x + 1] - known_values_slice[x]) * @as(u64, known_values_slice[y + 1] - known_values_slice[y]);
}
}
}
return result;
}
fn parseCommand(command: []const u8) RawCommand {
var index: usize = 2;
_ = readNumber(u8, command, &index);
index += 3;
const value = readHexNumber(u24, command, &index);
const direction: Direction = switch (value % 16) {
0 => .Right,
1 => .Down,
2 => .Left,
3 => .Up,
else => unreachable,
};
return .{
.length = value / 16,
.direction = direction,
};
}
fn solveAll(reader: anytype) !usize {
var result: usize = 0;
while (true) {
var commands = StackList(RawCommand, usize, 1024).init();
var empty_line_reached = false;
var line_buffer: [1000]u8 = undefined;
while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| {
if (line.len == 0) {
empty_line_reached = true;
break;
}
commands.add(parseCommand(line));
}
result += solveForCommands(commands.getSlice());
if (!empty_line_reached) {
return result;
}
}
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const raw_in = std.io.getStdIn();
var buffered_reader = std.io.bufferedReader(raw_in.reader());
var reader = buffered_reader.reader();
const result = try solveAll(&reader);
try stdout.print("{d}\n", .{result});
}