aoc25/day8/main.cpp
2025-12-18 22:05:27 +00:00

150 lines
No EOL
4.2 KiB
C++

#include <algorithm>
#include <cmath>
#include <expected>
#include <fstream>
#include <iostream>
#include <numeric>
#include <print>
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
// Use a simple struct for clarity
struct Point {
int x, y, z;
// Default spaceship operator for easy comparisons if needed
auto operator<=>(const Point &) const = default;
};
struct Edge {
int u; // Index of first point
int v; // Index of second point
double dist;
};
// --- Modern Union-Find (DSU) Implementation ---
struct DSU {
std::vector<int> parent;
int groups;
explicit DSU(size_t n) : parent(n), groups(static_cast<int>(n)) {
// Fill with 0, 1, 2, ..., n-1
std::iota(parent.begin(), parent.end(), 0);
}
// Find with Path Compression
// Returns the representative ID of the set containing i
auto find(int i) -> int {
if (parent[i] == i) {
return i;
}
return parent[i] = find(parent[i]);
}
// Unite two sets. Returns true if they were different sets.
auto unite(int i, int j) -> bool {
int root_i = find(i);
int root_j = find(j);
if (root_i != root_j) {
parent[root_i] = root_j; // Link roots
groups--;
return true;
}
return false;
}
};
// --- Helper Functions ---
static auto getDistance(const Point &a, const Point &b) -> double {
// std::hypot is cleaner (C++17) and avoids manual pow/sqrt
// For 3D: hypot(x, y, z) is C++17 specific overload
return std::hypot(a.x - b.x, a.y - b.y, a.z - b.z);
}
static auto parseInput(std::string_view filename) -> std::expected<std::vector<Point>, std::string> {
std::ifstream inputF{std::string(filename)};
if (!inputF) {
return std::unexpected{"Could not open file."};
}
std::vector<Point> points;
std::string line;
while (std::getline(inputF, line)) {
if (line.empty()) {
continue;
}
// Modern splitting and parsing
auto parts = line | std::views::split(',') | std::views::transform([](auto &&rng) -> auto {
// Convert range to string for stoi (C++23 common_range fix)
return std::string(rng.begin(), rng.end());
}) |
std::ranges::to<std::vector<std::string>>();
if (parts.size() >= 3) {
points.emplace_back(std::stoi(parts[0]), std::stoi(parts[1]), std::stoi(parts[2]));
}
}
return points;
}
static auto generateSortedEdges(const std::vector<Point> &points) -> std::vector<Edge> {
std::vector<Edge> edges;
edges.reserve((points.size() * (points.size() - 1)) / 2);
// Standard triangular loop is still clearer/faster than range combinatorics here
for (size_t i = 0; i < points.size(); ++i) {
for (size_t j = i + 1; j < points.size(); ++j) {
edges.push_back(
{.u = static_cast<int>(i), .v = static_cast<int>(j), .dist = getDistance(points[i], points[j])});
}
}
// Modern Sort with Projections
// Sorts by the 'dist' member automatically
std::ranges::sort(edges, {}, &Edge::dist);
return edges;
}
// --- Main Logic ---
static auto solvePartTwo(const std::vector<Point> &points) -> long long {
auto edges = generateSortedEdges(points);
DSU dsu(points.size());
for (const auto &[u, v, dist] : edges) {
// Try to unite the two points
if (dsu.unite(u, v)) {
// If this connection reduced us to exactly 1 group, we are done!
if (dsu.groups == 1) {
std::println("Connected all points! Last edge: {} <-> {}", u, v);
return (long long)points[u].x * (long long)points[v].x;
}
}
}
return -1; // Should not happen if graph is connected
}
auto main() -> int {
// Usage of std::string_view literal
auto puzzle = parseInput("puzzle_input");
if (!puzzle) {
std::println(stderr, "Error: {}", puzzle.error());
return 1;
}
std::println("Loaded {} points.", puzzle->size());
// Part 2 Logic
auto result = solvePartTwo(*puzzle);
std::println("Part 2 Result: {}", result);
return 0;
}