const std = @import("std"); const Error = error{IndexError}; const T = u64; const Box = struct { x1: T, x2: T, y1: T, y2: T, }; fn splitOnce(s: []u8, sep: u8) ![2][]u8 { const i = std.mem.indexOfScalar(u8, s, sep) orelse return Error.IndexError; return .{ s[0..i], s[i + 1 ..] }; } fn getBox(p1: [2]T, p2: [2]T) Box { return .{ .x1 = @min(p1[0], p2[0]), .x2 = @max(p1[0], p2[0]), .y1 = @min(p1[1], p2[1]), .y2 = @max(p1[1], p2[1]), }; } pub fn main() !void { const allocator = std.heap.smp_allocator; const path: [:0]const u8 = std.mem.span(std.os.argv[1]); var file = try std.fs.cwd().openFile(path, .{}); defer file.close(); var buffer: [1024]u8 = undefined; var reader = file.reader(&buffer); var points = std.ArrayList([2]T).empty; defer points.deinit(allocator); while (reader.interface.peekDelimiterExclusive('\n')) |line| { reader.interface.toss(line.len + 1); const s1, const s2 = try splitOnce(line, ','); const x = try std.fmt.parseInt(T, s1, 10); const y = try std.fmt.parseInt(T, s2, 10); try points.append(allocator, .{ x, y }); } else |err| switch (err) { error.EndOfStream => {}, else => |e| return e, } // this task is significantly easier if the line never does weird // things like walking back on itself. for (0..points.items.len) |i| { const p1 = points.items[i]; const p3 = points.items[(i + 2) % points.items.len]; std.debug.assert(p1[0] != p3[0]); std.debug.assert(p1[1] != p3[1]); } var part1: T = 0; var part2: T = 0; for (points.items, 0..) |p1, i| { boxes: for (0..i) |j| { const box = getBox(p1, points.items[j]); const a = (box.x2 - box.x1 + 1) * (box.y2 - box.y1 + 1); part1 = @max(part1, a); if (a > part2) { for (0..points.items.len) |k| { const edge = getBox( points.items[k], points.items[(k + 1) % points.items.len], ); if (edge.x1 < box.x2 and edge.x2 > box.x1 and edge.y1 < box.y2 and edge.y2 > box.y1) { continue :boxes; } } part2 = a; } } } std.debug.print("part1: {}\n", .{part1}); std.debug.print("part2: {}\n", .{part2}); }