diff --git a/userspace/aoc2025/CMakeLists.txt b/userspace/aoc2025/CMakeLists.txt index aa7962d3..68d2aa03 100644 --- a/userspace/aoc2025/CMakeLists.txt +++ b/userspace/aoc2025/CMakeLists.txt @@ -7,6 +7,7 @@ set(AOC2025_PROJECTS day6 day7 day8 + day9 full ) diff --git a/userspace/aoc2025/day9/CMakeLists.txt b/userspace/aoc2025/day9/CMakeLists.txt new file mode 100644 index 00000000..baeb765e --- /dev/null +++ b/userspace/aoc2025/day9/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES + main.cpp +) + +add_executable(aoc2025_day9 ${SOURCES}) +banan_include_headers(aoc2025_day9 ban) +banan_link_library(aoc2025_day9 libc) + +install(TARGETS aoc2025_day9 OPTIONAL) diff --git a/userspace/aoc2025/day9/main.cpp b/userspace/aoc2025/day9/main.cpp new file mode 100644 index 00000000..fbe2b621 --- /dev/null +++ b/userspace/aoc2025/day9/main.cpp @@ -0,0 +1,188 @@ +#include +#include +#include + +#include +#include + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +struct Point +{ + i64 x, y; +}; + +static BAN::Vector parse_points(FILE* fp) +{ + BAN::Vector points; + + i64 x, y; + while (fscanf(fp, "%" SCNi64 ",%" SCNi64, &x, &y) == 2) + MUST(points.emplace_back(x, y)); + + return points; +} + +i64 part1(FILE* fp) +{ + auto points = parse_points(fp); + + i64 result = 0; + + for (size_t i = 0; i < points.size(); i++) + { + for (size_t j = i + 1; j < points.size(); j++) + { + const i64 w = BAN::Math::abs(points[i].x - points[j].x) + 1; + const i64 h = BAN::Math::abs(points[i].y - points[j].y) + 1; + result = BAN::Math::max(result, w * h); + } + } + + return result; +} + +static BAN::Vector compress_coordinates(BAN::Vector& points) +{ + BAN::HashMap coord_map; + MUST(coord_map.reserve(points.size() * 4)); + for (size_t i = 0; i < points.size(); i++) + { + MUST(coord_map.emplace_or_assign(points[i].x)); + MUST(coord_map.emplace_or_assign(points[i].y)); + } + + BAN::Vector coords; + MUST(coords.reserve(coord_map.size())); + for (auto [coord, _] : coord_map) + MUST(coords.push_back(coord)); + BAN::sort::sort(coords.begin(), coords.end()); + + for (size_t i = 0; i < coords.size(); i++) + coord_map[coords[i]] = i; + + for (auto& point : points) + { + point.x = coord_map[point.x]; + point.y = coord_map[point.y]; + } + + return coords; +} + +i64 part2(FILE* fp) +{ + auto points = parse_points(fp); + auto index2coord = compress_coordinates(points); + + const size_t grid_size = index2coord.size(); + + BAN::Vector grid; + MUST(grid.resize(grid_size * grid_size, false)); + + for (size_t i = 0; i < points.size(); i++) + { + const auto curr = points[i]; + const auto next = points[(i + 1) % points.size()]; + ASSERT((curr.x == next.x) != (curr.y == next.y)); + + const i64 minx = BAN::Math::min(curr.x, next.x); + const i64 maxx = BAN::Math::max(curr.x, next.x); + const i64 miny = BAN::Math::min(curr.y, next.y); + const i64 maxy = BAN::Math::max(curr.y, next.y); + + const i64 dx = (minx != maxx); + const i64 dy = (miny != maxy); + + for (i64 x = minx, y = miny; x <= maxx && y <= maxy; x += dx, y += dy) + grid[y * grid_size + x] = true; + } + + for (size_t y = 0; y < grid_size; y++) + { + bool is_inside = false; + for (size_t x = 0; x < grid_size; x++) + { + if (!grid[y * grid_size + x]) + grid[y * grid_size + x] = is_inside; + else if (x + 1 >= grid_size || !grid[y * grid_size + x + 1]) + is_inside = !is_inside; + else + { + const bool from_below1 = (y + 1 < grid_size) && grid[(y + 1) * grid_size + x]; + while (x + 1 < grid_size && grid[y * grid_size + x + 1]) + x++; + const bool from_below2 = (y + 1 < grid_size) && grid[(y + 1) * grid_size + x]; + + if (from_below1 != from_below2) + is_inside = !is_inside; + } + } + } + + const auto is_usable_area = + [&grid, grid_size](const Point& a, const Point& b) -> bool + { + const i64 minx = BAN::Math::min(a.x, b.x); + const i64 maxx = BAN::Math::max(a.x, b.x); + const i64 miny = BAN::Math::min(a.y, b.y); + const i64 maxy = BAN::Math::max(a.y, b.y); + for (i64 x = minx; x <= maxx; x++) + if (!grid[miny * grid_size + x] || !grid[maxy * grid_size + x]) + return false; + for (i64 y = miny; y <= maxy; y++) + if (!grid[y * grid_size + minx] || !grid[y * grid_size + maxy]) + return false; + return true; + }; + + i64 result = 0; + + for (size_t i = 0; i < points.size(); i++) + { + for (size_t j = i + 1; j < points.size(); j++) + { + const auto& a = points[i]; + const auto& b = points[j]; + if (!is_usable_area(a, b)) + continue; + + const i64 w = BAN::Math::abs(index2coord[a.x] - index2coord[b.x]) + 1; + const i64 h = BAN::Math::abs(index2coord[a.y] - index2coord[b.y]) + 1; + result = BAN::Math::max(result, w * h); + } + } + + return result; +} + +int main(int argc, char** argv) +{ + const char* file_path = "/usr/share/aoc2025/day9_input.txt"; + + if (argc >= 2) + file_path = argv[1]; + + FILE* fp = fopen(file_path, "r"); + if (fp == nullptr) + { + perror("fopen"); + return 1; + } + + printf("part1: %" PRId64 "\n", part1(fp)); + + fseek(fp, 0, SEEK_SET); + + printf("part2: %" PRId64 "\n", part2(fp)); + + fclose(fp); +}