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