Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024 Day 6 #16

Merged
merged 4 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions 2024/06/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{
.preferred_optimize_mode = .ReleaseFast,
});

const utils = b.dependency("utils", .{ .target = target, .optimize = optimize }).module("utils");

const exe = b.addExecutable(.{
.name = "day6",
.root_source_file = b.path("solve.zig"),
.target = target,
.optimize = optimize,
});

exe.root_module.addImport("utils", utils);
b.installArtifact(exe);

{ // Run
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);

run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
run_step.dependOn(&run_cmd.step);
}
{ // Test
const test_step = b.step("test", "Run unit tests");
const unit_tests = b.addTest(.{
.root_source_file = b.path("solve.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);

unit_tests.root_module.addImport("utils", utils);
test_step.dependOn(&run_unit_tests.step);
}
}
11 changes: 11 additions & 0 deletions 2024/06/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.{
.name = "day06",
.version = "0.1.0",
.dependencies = .{
.utils = .{
.url = "../utils/",
.hash = "122059863bba3b73097c2905eacfa772e5c9ad7cd6616270b08dfdf41e3d81900420",
},
},
.paths = .{ "build.zig", "build.zig.zon", "solve.zig" },
}
220 changes: 220 additions & 0 deletions 2024/06/solve.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
const std = @import("std");
const utils = @import("utils");

const PointSet = std.AutoArrayHashMapUnmanaged(Point, void);
const InitialSituation = std.meta.Tuple(&.{ Point, Cardinal, PointSet, Point });
const Allocator = std.mem.Allocator;
const OrientedPoint = std.meta.Tuple(&.{ Point, Cardinal });
const OrientedPointSet = std.AutoHashMapUnmanaged(OrientedPoint, void);

const Cardinal = enum(u2) {
north,
west,
south,
east,

pub fn next(self: Cardinal) Cardinal {
return @enumFromInt(@intFromEnum(self) +% 1);
}
};

const Point = struct {
x: i32,
y: i32,

pub fn add(self: Point, other: Point) Point {
return .{ .x = self.x + other.x, .y = self.y + other.y };
}

pub fn neighbor(self: Point, direction: Cardinal) Point {
return switch (direction) {
.north => self.add(.{ .x = 0, .y = -1 }),
.west => self.add(.{ .x = 1, .y = 0 }),
.south => self.add(.{ .x = 0, .y = 1 }),
.east => self.add(.{ .x = -1, .y = 0 }),
};
}

pub fn isInBounds(self: Point, bounds: Point) bool {
return self.x >= 0 and self.y >= 0 and self.x < bounds.x and self.y < bounds.y;
}
};

const Guard = struct {
position: Point,
facing: Cardinal,
patrolArea: Point,

pub fn next(self: *Guard, obstacles: PointSet) ?OrientedPoint {
const peek = self.position.neighbor(self.facing);
if (!peek.isInBounds(self.patrolArea)) {
return null;
} else if (obstacles.contains(peek)) {
self.facing = self.facing.next();
} else {
self.position = peek;
}
return .{ self.position, self.facing };
}
};

pub fn parse(allocator: Allocator, input: anytype) !InitialSituation {
var obstacles: PointSet = .empty;
var guard: ?Point = null;
var lines = utils.lineIterator(input);
var bound_x: i32 = 0;
var y: i32 = 0;

while (lines.next()) |line| {
bound_x = @intCast(line.len);
for (line, 0..line.len) |c, x| {
if (c == '#') {
try obstacles.put(allocator, .{ .x = @intCast(x), .y = y }, {});
} else if (c == '^') {
guard = .{ .x = @intCast(x), .y = y };
}
}
y += 1;
}
return .{ guard.?, Cardinal.north, obstacles, .{ .x = bound_x, .y = y } };
}

fn part1(allocator: Allocator, guard: Guard, obstacles: PointSet) !u64 {
var visited: PointSet = .empty;
defer visited.deinit(allocator);
var patrol = guard;

try visited.ensureTotalCapacity(allocator, @intCast(guard.patrolArea.x * guard.patrolArea.y));
visited.putAssumeCapacity(guard.position, {});
while (patrol.next(obstacles)) |pos| {
visited.putAssumeCapacity(pos[0], {});
}
return visited.count();
}

fn part2(allocator: Allocator, guard: Guard, obstacles: PointSet) !u64 {
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const alloc = arena.allocator();

var pastStates: OrientedPointSet = .empty;
var visited: PointSet = .empty;
var possibleObstacle: PointSet = .empty;
var altPastStates: OrientedPointSet = .empty;
var altObstacles: PointSet = try obstacles.clone(alloc);

const area: u32 = @intCast(guard.patrolArea.x * guard.patrolArea.y);
var patrol = guard;
var previous = guard.position;

try pastStates.ensureTotalCapacity(alloc, area);
try visited.ensureTotalCapacity(alloc, area);
try altObstacles.ensureUnusedCapacity(alloc, 1);
try altPastStates.ensureTotalCapacity(alloc, area);
try possibleObstacle.ensureTotalCapacity(alloc, 2 * obstacles.count());

pastStates.putAssumeCapacity(.{ guard.position, guard.facing }, {});
visited.putAssumeCapacity(guard.position, {});
while (patrol.next(obstacles)) |pos| {
pastStates.putAssumeCapacity(pos, {});
if (!std.meta.eql(previous, pos[0])) {
if (!visited.contains(pos[0])) {
var alternative = Guard{ .position = previous, .facing = pos[1].next(), .patrolArea = guard.patrolArea };
altObstacles.putAssumeCapacity(pos[0], {});
while (alternative.next(altObstacles)) |alt| {
if (pastStates.contains(alt) or altPastStates.contains(alt)) {
possibleObstacle.putAssumeCapacity(pos[0], {});
break;
}
altPastStates.putAssumeCapacity(alt, {});
}
altPastStates.clearRetainingCapacity();
_ = altObstacles.swapRemove(pos[0]);
}
visited.putAssumeCapacity(pos[0], {});
previous = pos[0];
}
}

// for (0..@intCast(guard.patrolArea.y)) |y| {
// for (0..@intCast(guard.patrolArea.x)) |x| {
// const p = Point{ .x = @intCast(x), .y = @intCast(y) };
// if (obstacles.contains(p)) {
// std.debug.print("#", .{});
// } else if (possibleObstacle.contains(p)) {
// std.debug.print("O", .{});
// } else std.debug.print(".", .{});
// }
// std.debug.print("\n", .{});
// }

return possibleObstacle.count();
}

pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

var stdin = std.io.bufferedReader(std.io.getStdIn().reader());

const guardPos, const guardDir, var obstacles, const bounds = try parse(allocator, stdin.reader());
defer obstacles.deinit(allocator);
const guard = Guard{ .position = guardPos, .facing = guardDir, .patrolArea = bounds };

var timer = try std.time.Timer.start();
std.debug.print("Number of cells visited : {:5}\n", .{try part1(allocator, guard, obstacles)});
std.debug.print("{}\n", .{std.fmt.fmtDuration(timer.lap())});
std.debug.print("Number of possible loops: {:5}\n", .{try part2(allocator, guard, obstacles)});
std.debug.print("{}\n", .{std.fmt.fmtDuration(timer.lap())});
}

// -------------------- Tests --------------------

test {
const sample =
\\....#.....
\\.........#
\\..........
\\..#.......
\\.......#..
\\..........
\\.#..^.....
\\........#.
\\#.........
\\......#...
;
var stream = std.io.fixedBufferStream(sample);
const guardPos, const guardDir, var obstacles, const bounds = try parse(std.testing.allocator, stream.reader());
defer obstacles.deinit(std.testing.allocator);

try std.testing.expectEqual(Point{ .x = 4, .y = 6 }, guardPos);
try std.testing.expectEqual(Point{ .x = 10, .y = 10 }, bounds);
try std.testing.expectEqual(.north, guardDir);

const guard = Guard{ .position = guardPos, .facing = guardDir, .patrolArea = bounds };

try std.testing.expectEqual(41, try part1(std.testing.allocator, guard, obstacles));
try std.testing.expectEqual(6, try part2(std.testing.allocator, guard, obstacles));
}

test "Bruh" {
const sample =
\\..#.............
\\..............#.
\\...#............
\\........#.......
\\................
\\.......#.....#..
\\................
\\..^.............
;
var stream = std.io.fixedBufferStream(sample);
const guardPos, const guardDir, var obstacles, const bounds = try parse(std.testing.allocator, stream.reader());
defer obstacles.deinit(std.testing.allocator);

const guard = Guard{ .position = guardPos, .facing = guardDir, .patrolArea = bounds };

try std.testing.expectEqual(33, try part1(std.testing.allocator, guard, obstacles));
try std.testing.expectEqual(1, try part2(std.testing.allocator, guard, obstacles));
}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ My solutions for the [Advent of Code](https://adventofcode.com), a challenge sta
| 03 |:star::star:|:star::star:||:star::star:|:star::star:|:star::star:|:star::star:|:star::star:|:star::star:
| 04 |:star::star:|:star::star:||:star::star:|:star::star:|:star::star:|:star::star:|:star::star:|:star::star:
| 05 |:star::star:|:star::star:||:star::star:|:star::star:|:star::star:|:star::star:|:star::star:|:star::star:
| 06 ||:star::star:|||:star::star:|:star::star:|:star::star:|:star::star:|
| 06 ||:star::star:|||:star::star:|:star::star:|:star::star:|:star::star:|:star::star:
| 07 ||:star::star:|||:star::star:|:star::star:|:star::star:|:star::star:|
| 08 |||||:star::star:|:star::star:|:star::star:|:star::star:|
| 09 |||||:star::star:|:star::star:|:star::star:|:star::star:|
Expand All @@ -30,6 +30,6 @@ My solutions for the [Advent of Code](https://adventofcode.com), a challenge sta
| 23 |||||||:star::star:|:star:|
| 24 ||||||:star::star:|:star::star:|:star:|
| 25 |||||:star:|:star:|:star::star:||
| Total | 10 | 14 | 4 | 10 | 42 | 44 | 50 | 44 | 10
| Total | 10 | 14 | 4 | 10 | 42 | 44 | 50 | 44 | 12

Total stars: 228
Total stars: 230