aoc2024: Implement day23 solution
I'm not proud of my implementation to last 2 days, but they work in a reasonable time. I might optimize them later... Problem is largely with my hash structures. Preallocating a lot of elements before running day22 makes it run in less than half of the current time.
This commit is contained in:
parent
af8156f310
commit
d035068982
|
@ -21,6 +21,7 @@ set(AOC2024_PROJECTS
|
|||
day20
|
||||
day21
|
||||
day22
|
||||
day23
|
||||
full
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,171 @@
|
|||
#include <BAN/HashMap.h>
|
||||
#include <BAN/HashSet.h>
|
||||
#include <BAN/Sort.h>
|
||||
#include <BAN/String.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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<u32, BAN::Vector<u32>> connections;
|
||||
BAN::HashSet<u32> all_nodes;
|
||||
};
|
||||
|
||||
static ParseInputResult parse_input(FILE* fp)
|
||||
{
|
||||
BAN::HashMap<u32, BAN::Vector<u32>> connections;
|
||||
BAN::HashSet<u32> 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<u32, BAN::Vector<u32>>& connections, const BAN::Vector<u32>& 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<u32> maximum_clique(const BAN::HashMap<u32, BAN::Vector<u32>>& connections, const BAN::Vector<u32>& nodes, BAN::Vector<u32>&& current, usize idx)
|
||||
{
|
||||
if (idx == nodes.size())
|
||||
return current;
|
||||
|
||||
BAN::Vector<u32> result;
|
||||
|
||||
const u32 check = nodes[nodes.size() - idx - 1];
|
||||
if (is_connected_to_all_nodes(connections, current, check))
|
||||
{
|
||||
BAN::Vector<u32> 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<u32> 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);
|
||||
}
|
Loading…
Reference in New Issue