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.

274 lines
7.8 KiB

const std = @import("std");
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 has(self: *Self, needle: T) bool {
for (0..self.length) |i| {
if (self.mem[i] == needle) {
return true;
}
}
return false;
}
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 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;
}
const Hailstone = struct {
initial_x: i64,
initial_y: i64,
initial_z: i64,
velocity_x: i16,
velocity_y: i16,
velocity_z: i16,
};
const RationalNumber = struct {
numerator: i128,
denominator: i64,
fn init(numerator: i128, denominator: i64) RationalNumber {
std.debug.assert(denominator != 0);
if (denominator < 0) {
return .{
.numerator = -numerator,
.denominator = -denominator,
};
}
return .{
.numerator = numerator,
.denominator = denominator,
};
}
fn isNonNegative(self: *const RationalNumber) bool {
return self.numerator >= 0;
}
fn isIn(self: *const RationalNumber, first: i64, last: i64) bool {
return first * @as(i128, self.denominator) <= self.numerator and self.numerator <= last * @as(i128, self.denominator);
}
};
const ProjectionIntersection = struct {
x: RationalNumber,
y: RationalNumber,
time_first: RationalNumber,
time_second: RationalNumber,
};
const TestArea = struct {
x_first: i64,
x_last: i64,
y_first: i64,
y_last: i64,
};
fn getProjectionIntersection(a: Hailstone, b: Hailstone) ?ProjectionIntersection {
if (@as(i32, a.velocity_x) * @as(i32, b.velocity_y) == @as(i32, a.velocity_y) * @as(i32, b.velocity_x)) {
// projections of paths are parallel
if (@as(i128, a.initial_x - b.initial_x) * a.velocity_y == @as(i128, a.initial_y - b.initial_y) * a.velocity_x) {
// both paths are on the same line, the puzzle does not document this behavior
unreachable;
} else {
// paths are on different parallel lines, and never intersect
return null;
}
}
// set of linearly independent equations:
// t1 * a.velocity_x - t2 * b.velocity_x = b.initial_x - a.initial_x
// t1 * a.velocity_y - t2 * b.velocity_y = b.initial_y - a.initial_y
const determinant = -@as(i64, a.velocity_x) * b.velocity_y + @as(i64, a.velocity_y) * b.velocity_x;
const dt1 = -@as(i128, b.initial_x - a.initial_x) * b.velocity_y + @as(i128, b.initial_y - a.initial_y) * b.velocity_x;
const dt2 = a.velocity_x * @as(i128, b.initial_y - a.initial_y) - a.velocity_y * @as(i128, b.initial_x - a.initial_x);
const t1 = RationalNumber.init(dt1, determinant);
const t2 = RationalNumber.init(dt2, determinant);
const ax = RationalNumber.init(@as(i128, a.initial_x) * determinant + a.velocity_x * dt1, determinant);
const ay = RationalNumber.init(@as(i128, a.initial_y) * determinant + a.velocity_y * dt1, determinant);
const bx = RationalNumber.init(@as(i128, b.initial_x) * determinant + b.velocity_x * dt2, determinant);
const by = RationalNumber.init(@as(i128, b.initial_y) * determinant + b.velocity_y * dt2, determinant);
std.debug.assert(ax.numerator == bx.numerator);
std.debug.assert(ay.numerator == by.numerator);
return .{
.x = ax,
.y = ay,
.time_first = t1,
.time_second = t2,
};
}
fn solveForHailstones(hailstones: []const Hailstone, test_area: TestArea) usize {
var result: usize = 0;
for (0..hailstones.len) |i| {
for (0..i) |j| {
if (getProjectionIntersection(hailstones[i], hailstones[j])) |intersection| {
if (intersection.time_first.isNonNegative() and intersection.time_second.isNonNegative() and intersection.x.isIn(test_area.x_first, test_area.x_last) and intersection.y.isIn(test_area.y_first, test_area.y_last)) {
//std.debug.print("found intersection for {d} and {d}\n", .{ i, j });
result += 1;
}
}
}
}
return result;
}
fn parseFirstLine(line: []const u8) TestArea {
var i: usize = 0;
var x_first = readNumber(i64, line, &i);
i += 1;
var x_last = readNumber(i64, line, &i);
i += 1;
var y_first = readNumber(i64, line, &i);
i += 1;
var y_last = readNumber(i64, line, &i);
std.debug.assert(i >= line.len);
return .{
.x_first = x_first,
.x_last = x_last,
.y_first = y_first,
.y_last = y_last,
};
}
fn parseLine(line: []const u8) Hailstone {
var i: usize = 0;
var initial_x = readNumber(i64, line, &i);
i += 1;
var initial_y = readNumber(i64, line, &i);
i += 1;
var initial_z = readNumber(i64, line, &i);
i += 2;
var velocity_x = readNumber(i16, line, &i);
i += 1;
var velocity_y = readNumber(i16, line, &i);
i += 1;
var velocity_z = readNumber(i16, line, &i);
std.debug.assert(i >= line.len);
return .{
.initial_x = initial_x,
.initial_y = initial_y,
.initial_z = initial_z,
.velocity_x = velocity_x,
.velocity_y = velocity_y,
.velocity_z = velocity_z,
};
}
fn solveLines(lines: []const []const u8) usize {
var hailstones_list = StackList(Hailstone, usize, 400).init();
const test_area = parseFirstLine(lines[0]);
for (lines[1..]) |line| {
hailstones_list.add(parseLine(line));
}
return solveForHailstones(hailstones_list.getSlice(), test_area);
}
pub fn solveAll(reader: anytype) !usize {
var result: usize = 0;
while (true) {
var allocator_buffer: [50000]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&allocator_buffer);
var allocator = fba.allocator();
var lines = StackList([]u8, usize, 1500).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;
}
lines.add(try allocator.dupe(u8, line));
}
result += solveLines(lines.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});
}