Compare commits

...

2 Commits

Author SHA1 Message Date
4c30b52548 solve day 25 2024-06-02 15:58:12 +03:00
d52e9bc73e solve day 24 2024-06-02 12:30:00 +03:00
4 changed files with 515 additions and 3 deletions

View File

@ -275,7 +275,7 @@ fn compareQueueItem(_: void, a: QueueItem, b: QueueItem) std.math.Order {
return std.math.order(b.distance, a.distance);
}
fn find_longest_path(allocator: Allocator, node_map: NodeMap, start: PointU32, end: PointU32, max_best_distances: ?u32) !u64 {
fn findLongestPath(allocator: Allocator, node_map: NodeMap, start: PointU32, end: PointU32, max_best_distances: ?u32) !u64 {
const start_region = node_map.region_map[node_map.width * start.y + start.x].?;
const end_region = node_map.region_map[node_map.width * end.y + end.x].?;
@ -361,7 +361,7 @@ pub fn part1(input: *aoc.Input) !aoc.Result {
.y = map.height-1
};
const answer = try find_longest_path(allocator, node_map, start, end, null);
const answer = try findLongestPath(allocator, node_map, start, end, null);
return .{ .uint = answer };
}
@ -392,7 +392,7 @@ pub fn part2(input: *aoc.Input) !aoc.Result {
.y = map.height-1
};
const answer = try find_longest_path(allocator, node_map, end, start, 1000);
const answer = try findLongestPath(allocator, node_map, end, start, 1000);
return .{ .uint = answer };
}

246
src/day24.zig Normal file
View File

@ -0,0 +1,246 @@
const aoc = @import("./aoc.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const Point = @import("./point.zig").Point;
const Axis = enum { X, Y, Z };
fn Vec3(comptime T: type) type {
return struct {
x: T,
y: T,
z: T,
fn project(self: @This(), axis: Axis) Point(T) {
return switch (axis) {
.X => Point(T){ .x = self.y, .y = self.z },
.Y => Point(T){ .x = self.x, .y = self.z },
.Z => Point(T){ .x = self.x, .y = self.y },
};
}
fn sub(self: @This(), other: @This()) @This() {
return @This(){
.x = self.x - other.x,
.y = self.y - other.y,
.z = self.z - other.z,
};
}
fn add(self: @This(), other: @This()) @This() {
return @This(){
.x = self.x + other.x,
.y = self.y + other.y,
.z = self.z + other.z,
};
}
fn mul(self: @This(), multiplier: T) @This() {
return @This(){
.x = self.x * multiplier,
.y = self.y * multiplier,
.z = self.z * multiplier,
};
}
fn cross(self: @This(), other: @This()) @This() {
return @This(){
.x = self.y * other.z - self.z * other.y,
.y = self.z * other.x - self.x * other.z,
.z = self.x * other.y - self.y * other.x,
};
}
fn dot(self: @This(), other: @This()) T {
return self.x * other.x + self.y * other.y + self.z * other.z;
}
fn cast(self: @This(), comptime to: type) Vec3(to) {
return Vec3(to){
.x = self.x,
.y = self.y,
.z = self.z,
};
}
};
}
const Hailstone = struct {
position: Vec3(i64),
velocity: Vec3(i64),
};
fn parseVec3(str: []const u8) !Vec3(i64) {
var components: [3]i64 = undefined;
var count: u32 = 0;
var iter = std.mem.splitScalar(u8, str, ',');
while (iter.next()) |part| {
if (count == 3) return error.TooManyComponents;
components[count] = try std.fmt.parseInt(i64, std.mem.trim(u8, part, " "), 10);
count += 1;
}
if (count < 3) {
return error.MissingComponents;
}
return Vec3(i64){
.x = components[0],
.y = components[1],
.z = components[2],
};
}
fn parseInput(allocator: Allocator, lines: []const []const u8) !std.ArrayList(Hailstone) {
var parsed = std.ArrayList(Hailstone).init(allocator);
errdefer parsed.deinit();
for (lines) |line| {
const separator = std.mem.indexOfScalar(u8, line, '@') orelse return error.NoSeparator;
try parsed.append(Hailstone{
.position = try parseVec3(line[0..separator]),
.velocity = try parseVec3(line[(separator+1)..])
});
}
return parsed;
}
fn getIntersect(p1: Point(f64), v1: Point(f64), p2: Point(f64), v2: Point(f64)) ?Point(f64) {
const t = -(
((p1.x - p2.x) * v2.y - (p1.y - p2.y) * v2.x) /
(v1.x * v2.y - v1.y * v2.x)
);
const u = (
(v1.x * (p1.y - p2.y) - v1.y * (p1.x - p2.x)) /
(v1.x * v2.y - v1.y * v2.x)
);
const px = p1.x + t * v1.x;
const py = p1.y + t * v1.y;
if (t >= 0 and u >= 0) {
return Point(f64){ .x = px, .y = py };
} else {
return null;
}
}
fn vec3FloatFromInt(p: Vec3(i64)) Vec3(f64) {
return Vec3(f64){
.x = @floatFromInt(p.x),
.y = @floatFromInt(p.y),
.z = @floatFromInt(p.z),
};
}
pub fn part1(input: *aoc.Input) !aoc.Result {
const hailstones = try parseInput(input.allocator, input.lines);
defer hailstones.deinit();
const min_test_area = 200000000000000;
const max_test_area = 400000000000000;
var answer: u32 = 0;
for (0..(hailstones.items.len-1)) |i| {
const hailstone1 = hailstones.items[i];
for ((i+1)..hailstones.items.len) |j| {
const hailstone2 = hailstones.items[j];
const p1 = vec3FloatFromInt(hailstone1.position);
const v1 = vec3FloatFromInt(hailstone1.velocity);
const p2 = vec3FloatFromInt(hailstone2.position);
const v2 = vec3FloatFromInt(hailstone2.velocity);
const p = getIntersect(
.{ .x = p1.x, .y = p1.y },
.{ .x = v1.x, .y = v1.y },
.{ .x = p2.x, .y = p2.y },
.{ .x = v2.x, .y = v2.y }
) orelse continue;
if (min_test_area <= p.x and p.x <= max_test_area and min_test_area <= p.y and p.y <= max_test_area) {
answer += 1;
}
}
}
return .{ .uint = answer };
}
fn getIntersectTime(comptime T: type, p1: Vec3(T), v1: Vec3(T), p2: Vec3(T), v2: Vec3(T)) !T {
const place_normal = p1.cross(p1.add(v1));
const denominator = v2.dot(place_normal);
if (denominator == 0) {
return error.Parallel;
}
const numerator = -p2.dot(place_normal);
if (@mod(numerator, denominator) != 0) {
return error.NonInteger;
}
return @divExact(numerator, denominator);
}
// This helped a LOT, really nice visualization and explanation of the math
// https://aidiakapi.com/blog/2024-01-20-advent-of-code-2023-day-24/
pub fn part2(input: *aoc.Input) !aoc.Result {
const hailstones = try parseInput(input.allocator, input.lines);
defer hailstones.deinit();
var answer: u64 = 0;
var triplet_iter = std.mem.window(Hailstone, hailstones.items, 3, 1);
while (triplet_iter.next()) |triplet| {
const r = triplet[0]; // Reference point
const a = triplet[1]; // A point
const b = triplet[2]; // B point
const r_p = r.position.cast(i128);
const r_v = r.velocity.cast(i128);
const a_p = a.position.cast(i128);
const a_v = a.velocity.cast(i128);
const b_p = b.position.cast(i128);
const b_v = b.velocity.cast(i128);
const ra_p = a_p.sub(r_p); // A position relative to reference
const rb_p = b_p.sub(r_p); // A velocity relative to reference
const ra_v = a_v.sub(r_v); // B position relative to reference
const rb_v = b_v.sub(r_v); // B velocity relative to reference
// Check "Plane vs. Line" intersection for A and B
const t1 = getIntersectTime(i128, rb_p, rb_v, ra_p, ra_v) catch continue;
const t2 = getIntersectTime(i128, ra_p, ra_v, rb_p, rb_v) catch continue;
// Convert the intersection time into a global position
// From this point on reference point is not needed
const c1 = a_p.add(a_v.mul(t1));
const c2 = b_p.add(b_v.mul(t2));
const delta_time = t2 - t1;
const delta_intersect = c2.sub(c1);
if (@mod(delta_intersect.x, delta_time) != 0) continue;
if (@mod(delta_intersect.y, delta_time) != 0) continue;
if (@mod(delta_intersect.z, delta_time) != 0) continue;
const v = Vec3(i128){
.x = @divExact(delta_intersect.x, delta_time),
.y = @divExact(delta_intersect.y, delta_time),
.z = @divExact(delta_intersect.z, delta_time),
};
const p = c1.sub(v.mul(t1));
answer = @intCast(p.x + p.y + p.z);
break;
}
return .{ .uint = answer };
}

262
src/day25.zig Normal file
View File

@ -0,0 +1,262 @@
const aoc = @import("./aoc.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const max_connections = 16;
const ComponentName = std.BoundedArray(u8, 3);
const WiringComponent = struct {
name: ComponentName,
connected_to: std.BoundedArray(ComponentName, max_connections),
pub fn format(
self: WiringComponent,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
try writer.print("{s}:", .{ self.name.constSlice() });
for (self.connected_to.constSlice()) |other_name| {
try writer.print(" {s}", .{ other_name.constSlice() });
}
}
};
fn parseComponent(line: []const u8) !WiringComponent {
const name_separator = std.mem.indexOfScalar(u8, line, ':') orelse return error.NoSeparator;
var connected_to = std.BoundedArray(ComponentName, max_connections).init(0) catch unreachable;
var connected_iter = std.mem.splitScalar(u8, line[(name_separator+1)..], ' ');
while (connected_iter.next()) |part| {
const part_trimmed = std.mem.trim(u8, part, " ");
if (part_trimmed.len == 0) continue;
try connected_to.append(try ComponentName.fromSlice(part_trimmed));
}
return WiringComponent{
.name = try ComponentName.fromSlice(line[0..name_separator]),
.connected_to = connected_to
};
}
fn parseInput(allocator: Allocator, lines: []const []const u8) !std.ArrayList(WiringComponent) {
var components = std.ArrayList(WiringComponent).init(allocator);
errdefer components.deinit();
for (lines) |line| {
try components.append(try parseComponent(line));
}
return components;
}
const ComponentId = u11;
const ComponentGraph = struct {
const ConnectionList = std.BoundedArray(ComponentId, max_connections);
allocator: Allocator,
names: std.ArrayList(ComponentName),
connections: []ConnectionList,
fn init(allocator: Allocator, components: []WiringComponent) !ComponentGraph {
var names = std.ArrayList(ComponentName).init(allocator);
errdefer names.deinit();
for (components) |component| {
if (getComponentId(names.items, component.name.constSlice()) == null) {
try names.append(component.name);
}
for (component.connected_to.constSlice()) |other_name| {
if (getComponentId(names.items, other_name.constSlice()) == null) {
try names.append(other_name);
}
}
}
const connections = try allocator.alloc(ConnectionList, names.items.len);
errdefer allocator.free(connections);
for (connections) |*connection| {
connection.* = ConnectionList.init(0) catch unreachable;
}
const self = ComponentGraph{
.allocator = allocator,
.names = names,
.connections = connections
};
for (components) |component| {
const id: ComponentId = getComponentId(names.items, component.name.constSlice()) orelse unreachable;
for (component.connected_to.constSlice()) |other_name| {
const other_id = getComponentId(names.items, other_name.constSlice()) orelse unreachable;
if (std.mem.indexOfScalar(ComponentId, connections[id].constSlice(), other_id) == null) {
try connections[id].append(other_id);
}
if (std.mem.indexOfScalar(ComponentId, connections[other_id].constSlice(), id) == null) {
try connections[other_id].append(id);
}
}
}
return self;
}
fn getComponentId(names: []ComponentName, target: []const u8) ?ComponentId {
for (0.., names) |id, name| {
if (std.mem.eql(u8, target, name.constSlice())) {
return @intCast(id);
}
}
return null;
}
fn getName(self: ComponentGraph, id: ComponentId) []const u8 {
return self.names.items[id].constSlice();
}
fn deinit(self: ComponentGraph) void {
self.names.deinit();
self.allocator.free(self.connections);
}
};
fn findPath(allocator: Allocator, used_edges: []bool, graph: ComponentGraph, start: ComponentId, end: ComponentId) !?std.ArrayList(ComponentId) {
const parent = try allocator.alloc(?ComponentId, graph.names.items.len);
defer allocator.free(parent);
@memset(parent, null);
var stack = std.ArrayList(ComponentId).init(allocator);
defer stack.deinit();
try stack.append(start);
parent[start] = start;
const node_count = graph.names.items.len;
while (stack.popOrNull()) |node_id| {
if (node_id == end) {
break;
}
for (graph.connections[node_id].constSlice()) |other_node_id| {
const visited_index = node_count * node_id + other_node_id;
if (parent[other_node_id] == null and !used_edges[visited_index]) {
parent[other_node_id] = node_id;
try stack.append(other_node_id);
}
}
}
if (parent[end] == null) {
return null;
}
var result = std.ArrayList(ComponentId).init(allocator);
errdefer result.deinit();
try result.append(end);
var current: ?ComponentId = end;
while (true) {
current = parent[current.?];
try result.insert(0, current.?);
if (current.? == start) break;
}
return result;
}
fn countPaths(allocator: Allocator, graph: ComponentGraph, used_edges: []bool, from: ComponentId, to: ComponentId, max_paths: u32) !u32 {
const node_count = graph.names.items.len;
var path_count: u32 = 0;
while (path_count < max_paths) {
const maybe_path = try findPath(allocator, used_edges, graph, from, to);
if (maybe_path == null) break;
const path = maybe_path.?;
defer path.deinit();
for (0..(path.items.len-1)) |i| {
const segment_from = path.items[i];
const segment_to = path.items[i+1];
used_edges[segment_from * node_count + segment_to] = true;
}
path_count += 1;
}
return path_count;
}
fn getIslandSize(allocator: Allocator, graph: ComponentGraph, used_edges: []bool, from: ComponentId) !usize {
const visisted = try allocator.alloc(bool, graph.names.items.len);
defer allocator.free(visisted);
@memset(visisted, false);
var stack = std.ArrayList(ComponentId).init(allocator);
defer stack.deinit();
try stack.append(from);
visisted[from] = true;
const node_count = graph.names.items.len;
while (stack.popOrNull()) |node_id| {
for (graph.connections[node_id].constSlice()) |other_node_id| {
const visited_index = node_count * node_id + other_node_id;
if (!visisted[other_node_id] and !used_edges[visited_index]) {
visisted[other_node_id] = true;
try stack.append(other_node_id);
}
}
}
return std.mem.count(bool, visisted, &[_]bool{ true });
}
pub fn part1(input: *aoc.Input) !aoc.Result {
const allocator = input.allocator;
const components = try parseInput(allocator, input.lines);
defer components.deinit();
const graph = try ComponentGraph.init(allocator, components.items);
defer graph.deinit();
// std.debug.print("Graph:\n", .{});
// for (0.., graph.connections) |node_id, connections| {
// std.debug.print("{s} ->", .{graph.getName(@intCast(node_id))});
// for (connections.constSlice()) |connection| {
// std.debug.print(" {s}", .{graph.getName(@intCast(connection))});
// }
// std.debug.print("\n", .{});
// }
var source_node: ?ComponentId = null;
var target_node: ?ComponentId = null;
const node_count = graph.names.items.len;
const used_edges = try allocator.alloc(bool, node_count * node_count);
defer allocator.free(used_edges);
for (0..(node_count-1)) |i| {
for ((i+1)..node_count) |j| {
@memset(used_edges, false);
const path_count = try countPaths(allocator, graph, used_edges, @intCast(i), @intCast(j), 4);
if (path_count == 3) {
source_node = @intCast(i);
target_node = @intCast(j);
break;
}
}
}
if (source_node == null or target_node == null) return error.NoSolution;
const island_size1 = try getIslandSize(allocator, graph, used_edges, source_node.?);
const island_size2 = node_count - island_size1;
return .{ .uint = @intCast(island_size1 * island_size2) };
}

View File

@ -44,6 +44,8 @@ const Days = [_]aoc.Day{
create_day(@import("./day21.zig")),
create_day(@import("./day22.zig")),
create_day(@import("./day23.zig")),
create_day(@import("./day24.zig")),
create_day(@import("./day25.zig")),
};
fn kilobytes(count: u32) u32 {
@ -210,4 +212,6 @@ test {
_ = @import("./day21.zig");
_ = @import("./day22.zig");
_ = @import("./day23.zig");
_ = @import("./day24.zig");
_ = @import("./day25.zig");
}