diff --git a/userspace/aoc2023/CMakeLists.txt b/userspace/aoc2023/CMakeLists.txt index c32a1907..6aee4858 100644 --- a/userspace/aoc2023/CMakeLists.txt +++ b/userspace/aoc2023/CMakeLists.txt @@ -26,6 +26,7 @@ set(AOC2023_PROJECTS day21 day23 day24 + day25 full ) diff --git a/userspace/aoc2023/day25/CMakeLists.txt b/userspace/aoc2023/day25/CMakeLists.txt new file mode 100644 index 00000000..dea869bc --- /dev/null +++ b/userspace/aoc2023/day25/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.26) + +project(aoc2023_day25 CXX) + +set(SOURCES + main.cpp +) + +add_executable(aoc2023_day25 ${SOURCES}) +target_compile_options(aoc2023_day25 PUBLIC -O2 -g) +target_link_libraries(aoc2023_day25 PUBLIC libc ban) + +add_dependencies(aoc2023_day25 libc-install ban-install) + +add_custom_target(aoc2023_day25-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/aoc2023_day25 ${BANAN_AOC2023_BIN}/day25 + DEPENDS aoc2023_day25 + DEPENDS aoc2023_always +) + +add_dependencies(aoc2023 aoc2023_day25) +add_dependencies(aoc2023-install aoc2023_day25-install) diff --git a/userspace/aoc2023/day25/main.cpp b/userspace/aoc2023/day25/main.cpp new file mode 100644 index 00000000..23bebc05 --- /dev/null +++ b/userspace/aoc2023/day25/main.cpp @@ -0,0 +1,249 @@ +#include +#include +#include +#include +#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 Component +{ + BAN::String name; + BAN::Vector connections; +}; + +BAN::HashMap parse_components(FILE* fp) +{ + BAN::HashMap components; + + char buffer[128]; + while (fgets(buffer, sizeof(buffer), fp)) + { + BAN::StringView line(buffer); + ASSERT(line.back() == '\n'); + line = line.substring(0, line.size() - 1); + if (line.empty()) + break; + + auto parts = MUST(line.split(' ')); + ASSERT(parts.size() >= 2); + + ASSERT(parts.front().back() == ':'); + parts.front() = parts.front().substring(0, parts.front().size() - 1); + + if (!components.contains(parts.front())) + MUST(components.emplace(parts.front(), parts.front())); + + for (size_t i = 1; i < parts.size(); i++) + { + MUST(components[parts.front()].connections.emplace_back(parts[i])); + + if (!components.contains(parts[i])) + MUST(components.emplace(parts[i], parts[i])); + MUST(components[parts[i]].connections.emplace_back(parts.front())); + } + } + + return components; +} + +BAN::String connection_key(const BAN::String& a, const BAN::String& b) +{ + auto comp = + [](const auto& a, const auto& b) + { + ASSERT(a.size() == b.size()); + for (size_t i = 0; i < a.size(); i++) + if (a[i] != b[i]) + return a[i] < b[i]; + ASSERT_NOT_REACHED(); + }; + + const auto& s1 = comp(a, b) ? a : b; + const auto& s2 = comp(a, b) ? b : a; + + auto key = s1; + MUST(key.append(s2)); + return key; +} + +size_t graph_size(const BAN::HashMap& graph, const BAN::String& start, const BAN::HashSet& removed) +{ + BAN::HashSet visited; + BAN::HashSet pending; + MUST(pending.insert(start)); + + while (!pending.empty()) + { + auto current = *pending.begin(); + pending.remove(current); + + MUST(visited.insert(current)); + + const auto& targets = graph[current].connections; + for (const auto& target : targets) + { + if (removed.contains(connection_key(current, target))) + continue; + if (visited.contains(target)) + continue; + MUST(pending.insert(target)); + MUST(visited.insert(target)); + } + } + + return visited.size(); +} + +BAN::Vector find_shortest_path(const BAN::String& start, const BAN::String& end, const BAN::HashMap& graph) +{ + BAN::HashMap> paths; + MUST(paths.insert(start, {})); + MUST(paths[start].push_back(start)); + + BAN::HashSet visited; + BAN::HashSet pending; + MUST(pending.insert(start)); + + while (!pending.empty()) + { + BAN::HashSet next_pending; + + while (!pending.empty()) + { + auto current = *pending.begin(); + pending.remove(current); + MUST(visited.insert(current)); + + if (current == end) + return paths[current]; + + const auto& targets = graph[current].connections; + for (const auto& target : targets) + { + if (visited.contains(target)) + continue; + if (pending.contains(target)) + continue; + if (next_pending.contains(target)) + continue; + if (paths.contains(target)) + continue; + MUST(next_pending.insert(target)); + MUST(visited.insert(target)); + + MUST(paths.insert(target, paths[current])); + MUST(paths[target].push_back(target)); + } + } + + pending = BAN::move(next_pending); + } + + ASSERT_NOT_REACHED(); +} + +i64 puzzle1(FILE* fp) +{ + auto components = parse_components(fp); + + BAN::HashMap connection_count; + + srand(time(nullptr)); + for (size_t i = 0; i < 100; i++) + { + size_t idx1 = rand() % components.size(); + size_t idx2 = rand() % components.size(); + + auto path = find_shortest_path(next(components.begin(), idx1)->key, next(components.begin(), idx2)->key, components); + for (size_t j = 1; j < path.size(); j++) + { + const auto& connection = connection_key(path[j - 1], path[j]); + if (!connection_count.contains(connection)) + MUST(connection_count.insert(connection, 0)); + connection_count[connection]++; + } + } + + struct Connection + { + BAN::String connection; + i64 count; + }; + BAN::Vector hot_connections; + for (const auto& [connection, count] : connection_count) + MUST(hot_connections.emplace_back(connection, count)); + BAN::sort::sort(hot_connections.begin(), hot_connections.end(), [](const auto& a, const auto& b) { return a.count > b.count; }); + + for (size_t depth = 0; depth < hot_connections.size(); depth++) + { + for (size_t i = 0; i < depth; i++) + { + for (size_t j = i + 1; j < depth; j++) + { + for (size_t k = j + 1; k < depth; k++) + { + BAN::HashSet removed; + MUST(removed.insert(hot_connections[i].connection)); + MUST(removed.insert(hot_connections[j].connection)); + MUST(removed.insert(hot_connections[k].connection)); + + size_t lhs_size = graph_size(components, hot_connections[i].connection.sv().substring(0, 3), removed); + if (lhs_size == components.size()) + continue; + + size_t rhs_size = graph_size(components, hot_connections[i].connection.sv().substring(3, 3), removed); + if (rhs_size == components.size()) + continue; + + if (lhs_size + rhs_size == components.size()) + return lhs_size * rhs_size; + } + } + } + } + + return -1; +} + +i64 puzzle2(FILE* fp) +{ + (void)fp; + return -1; +} + +int main(int argc, char** argv) +{ + const char* file_path = "/usr/share/aoc2023/day25_input.txt"; + + if (argc >= 2) + file_path = argv[1]; + + FILE* fp = fopen(file_path, "r"); + if (fp == nullptr) + { + perror("fopen"); + return 1; + } + + printf("puzzle1: %" PRId64 "\n", puzzle1(fp)); + + fseek(fp, 0, SEEK_SET); + + printf("puzzle2: %" PRId64 "\n", puzzle2(fp)); + + fclose(fp); +} diff --git a/userspace/aoc2023/full/main.cpp b/userspace/aoc2023/full/main.cpp index d99104f4..58ca251a 100644 --- a/userspace/aoc2023/full/main.cpp +++ b/userspace/aoc2023/full/main.cpp @@ -4,7 +4,7 @@ int main() { - for (int i = 1; i <= 24; i++) + for (int i = 1; i <= 25; i++) { if (i == 22) continue;