diff --git a/userspace/aoc2024/CMakeLists.txt b/userspace/aoc2024/CMakeLists.txt index f7e0144a..fd0721a5 100644 --- a/userspace/aoc2024/CMakeLists.txt +++ b/userspace/aoc2024/CMakeLists.txt @@ -21,6 +21,7 @@ set(AOC2024_PROJECTS day20 day21 day22 + day23 full ) diff --git a/userspace/aoc2024/day23/CMakeLists.txt b/userspace/aoc2024/day23/CMakeLists.txt new file mode 100644 index 00000000..15572368 --- /dev/null +++ b/userspace/aoc2024/day23/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES + main.cpp +) + +add_executable(aoc2024_day23 ${SOURCES}) +banan_link_library(aoc2024_day23 ban) +banan_link_library(aoc2024_day23 libc) + +install(TARGETS aoc2024_day23 OPTIONAL) diff --git a/userspace/aoc2024/day23/main.cpp b/userspace/aoc2024/day23/main.cpp new file mode 100644 index 00000000..d68ccb1d --- /dev/null +++ b/userspace/aoc2024/day23/main.cpp @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +#include +#include + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; +using isize = ssize_t; + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; +using usize = size_t; + +struct ParseInputResult +{ + BAN::HashMap> connections; + BAN::HashSet all_nodes; +}; + +static ParseInputResult parse_input(FILE* fp) +{ + BAN::HashMap> connections; + BAN::HashSet all_nodes; + + char buffer[16]; + while (fgets(buffer, sizeof(buffer), fp)) + { + ASSERT(buffer[2] == '-' && buffer[5] == '\n'); + const u16 lhs = (buffer[0] << 8) | buffer[1]; + const u16 rhs = (buffer[3] << 8) | buffer[4]; + + const u16 min = BAN::Math::min(lhs, rhs); + const u16 max = BAN::Math::max(lhs, rhs); + + auto it = connections.find(min); + if (it == connections.end()) + it = MUST(connections.emplace(min)); + MUST(it->value.push_back(max)); + + MUST(all_nodes.insert(lhs)); + MUST(all_nodes.insert(rhs)); + } + + return { + .connections = BAN::move(connections), + .all_nodes = BAN::move(all_nodes), + }; +} + +i64 part1(FILE* fp) +{ + auto [connections, _] = parse_input(fp); + + u64 result = 0; + + for (const auto& [con1, dst1] : connections) + { + for (const u16 con2 : dst1) + { + auto it = connections.find(con2); + if (it == connections.end()) + continue; + for (const u16 con3 : it->value) + { + if (!dst1.contains(con3)) + continue; + result += + ((char)(con1 >> 8) == 't') || + ((char)(con2 >> 8) == 't') || + ((char)(con3 >> 8) == 't'); + } + } + } + + return result; +} + +static bool is_connected_to_all_nodes(const BAN::HashMap>& connections, const BAN::Vector& nodes, u32 test) +{ + if (nodes.empty()) + return true; + + auto it = connections.find(test); + if (it == connections.end()) + return false; + const auto& test_con = it->value; + + usize found = 0; + for (usize i = 0; i < test_con.size() && found < nodes.size(); i++) + if (test_con[i] == nodes[found]) + found++; + return found == nodes.size(); +} + +static BAN::Vector maximum_clique(const BAN::HashMap>& connections, const BAN::Vector& nodes, BAN::Vector&& current, usize idx) +{ + if (idx == nodes.size()) + return current; + + BAN::Vector result; + + const u32 check = nodes[nodes.size() - idx - 1]; + if (is_connected_to_all_nodes(connections, current, check)) + { + BAN::Vector temp; + MUST(temp.resize(current.size() + 1)); + for (usize i = 0; i < current.size(); i++) + temp[i + 1] = current[i]; + temp[0] = check; + result = maximum_clique(connections, nodes, BAN::move(temp), idx + 1); + } + + auto temp = maximum_clique(connections, nodes, BAN::move(current), idx + 1); + if (temp.size() > result.size()) + result = BAN::move(temp); + + return result; +} + +BAN::String part2(FILE* fp) +{ + auto [connections, set_all_nodes] = parse_input(fp); + + for (auto& [_, conn] : connections) + BAN::sort::sort(conn.begin(), conn.end()); + + BAN::Vector all_nodes; + MUST(all_nodes.reserve(set_all_nodes.size())); + for (const u32 node : set_all_nodes) + MUST(all_nodes.push_back(node)); + BAN::sort::sort(all_nodes.begin(), all_nodes.end()); + + auto clique = maximum_clique(connections, all_nodes, {}, 0); + + BAN::String result; + for (u32 node : clique) + MUST(result.append(MUST(BAN::String::formatted("{}{},", (char)(node >> 8), (char)node)))); + if (!result.empty()) + result.pop_back(); + return result; +} + +int main(int argc, char** argv) +{ + const char* file_path = "/usr/share/aoc2024/day23_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: %s\n", part2(fp).data()); + + fclose(fp); +}