diff --git a/day8/main.cpp b/day8/main.cpp index dcbdea8..d81e7c6 100644 --- a/day8/main.cpp +++ b/day8/main.cpp @@ -1,176 +1,150 @@ #include #include -#include #include -#include #include -#include #include -#include +#include #include #include #include -#include -#include +#include #include -using Point = std::tuple; +// 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 parent; + int groups; + + explicit DSU(size_t n) : parent(n), groups(static_cast(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 { - return sqrt(pow(std::get<0>(a) - std::get<0>(b), 2) + pow(std::get<1>(a) - std::get<1>(b), 2) + - pow(std::get<2>(a) - std::get<2>(b), 2)); + // 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(const std::string &filename) -> std::expected, std::string> { - std::ifstream inputF{filename}; - +static auto parseInput(std::string_view filename) -> std::expected, std::string> { + std::ifstream inputF{std::string(filename)}; if (!inputF) { - return std::unexpected{"Some file open error."}; + return std::unexpected{"Could not open file."}; } - std::vector pointVec{}; + std::vector points; + std::string line; - std::string puzzleLine; - while (std::getline(inputF, puzzleLine)) { - if (!puzzleLine.empty()) { - auto subParts = std::ranges::to>(std::views::split(puzzleLine, ',')); - pointVec.emplace_back(std::stoi(subParts[0]), std::stoi(subParts[1]), std::stoi(subParts[2])); + 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>(); + + if (parts.size() >= 3) { + points.emplace_back(std::stoi(parts[0]), std::stoi(parts[1]), std::stoi(parts[2])); } } - // std::println("pointList: {}", pointVec); - return pointVec; + return points; } -static auto getCombinations(const std::vector &pointVec) -> std::vector> { - std::vector> result; +static auto generateSortedEdges(const std::vector &points) -> std::vector { + std::vector edges; + edges.reserve((points.size() * (points.size() - 1)) / 2); - result.reserve(pointVec.size() * pointVec.size()); - - for (size_t i = 0; i < pointVec.size(); ++i) { - for (size_t j = i + 1; j < pointVec.size(); ++j) { - result.emplace_back(pointVec[i], pointVec[j]); + // 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(i), .v = static_cast(j), .dist = getDistance(points[i], points[j])}); } } - return std::move(result); + + // Modern Sort with Projections + // Sorts by the 'dist' member automatically + std::ranges::sort(edges, {}, &Edge::dist); + + return edges; } -static auto getDistances(const std::vector &pointVec) -> auto { - auto comparisonLamb = [](const std::pair &pair1, const std::pair &pair2) -> bool { - return getDistance(pair1.first, pair1.second) < getDistance(pair2.first, pair2.second); - }; - std::map, double, decltype(comparisonLamb)> sortedMap(comparisonLamb); +// --- Main Logic --- - auto combinations = getCombinations(pointVec); - for (const auto &combination : combinations) { +static auto solvePartTwo(const std::vector &points) -> long long { + auto edges = generateSortedEdges(points); + DSU dsu(points.size()); - // std::println("Combinations: {}:{}", combination.first, combination.second); - sortedMap[combination] = getDistance(combination.first, combination.second); - } - - return sortedMap; -} - -static auto doTheThing(const std::vector &pointVec, int numberToTake) -> long long { - auto distanceMap = getDistances(pointVec); - std::map pointToCircuitMap{}; - std::unordered_map> circuitMap; - int currentCircuit = 0; - for (const auto &[combination, distance] : distanceMap) { - const auto &[left, right] = combination; - // std::println("DistanceMap: {}:{} = {}", left, right, distance); - - if (pointToCircuitMap.contains(left) && pointToCircuitMap.contains(right)) { - int leftCircuit = pointToCircuitMap[left]; - int rightCircuit = pointToCircuitMap[right]; - // std::println("Left circuit: {}. Right circuit: {}", leftCircuit, rightCircuit); - if (leftCircuit != rightCircuit) { - std::println("Merge: {} to {}", left, right); - - for (const auto &rightCircuitPoint : circuitMap[rightCircuit]) { - pointToCircuitMap[rightCircuitPoint] = leftCircuit; - } - circuitMap[leftCircuit].append_range(circuitMap[rightCircuit]); - circuitMap.erase(rightCircuit); - - // std::println("mergesize: {}", circuitMap[leftCircuit].size()); - if (circuitMap[leftCircuit].size() >= pointVec.size()) { - - return (long long)std::get<0>(left) * (long long)std::get<0>(right); - } + 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; } - } else if (pointToCircuitMap.contains(left)) { - int leftCircuit = pointToCircuitMap[left]; - // std::println("Only left exists: {} to {}", left, right); - pointToCircuitMap[right] = pointToCircuitMap[left]; - - circuitMap[leftCircuit].push_back(right); - // std::println("leftsize: {}", circuitMap[leftCircuit].size()); - if (circuitMap[leftCircuit].size() >= pointVec.size()) { - - return (long long)std::get<0>(left) * (long long)std::get<0>(right); - } - } else if (pointToCircuitMap.contains(right)) { - int rightCircuit = pointToCircuitMap[right]; - // std::println("Only right exists: {} to {}", left, right); - pointToCircuitMap[left] = pointToCircuitMap[right]; - - circuitMap[rightCircuit].push_back(left); - // std::println("rightsize: {}", circuitMap[rightCircuit].size()); - if (circuitMap[rightCircuit].size() >= pointVec.size()) { - - return (long long)std::get<0>(left) * (long long)std::get<0>(right); - } - } else { - currentCircuit++; - pointToCircuitMap[left] = currentCircuit; - pointToCircuitMap[right] = currentCircuit; - - circuitMap[currentCircuit].push_back(left); - circuitMap[currentCircuit].push_back(right); } } - // std::println("pointToCircuitMap: {}", pointToCircuitMap); - std::println("circuitMap"); - for (const auto &[circuit, pointsInCircuit] : circuitMap) { - - std::println("{} : {}", circuit, pointsInCircuit); - } - - auto sizesOfLists = circuitMap | std::views::transform([](const auto &kvp) -> int { return kvp.second.size(); }) | - std::ranges::to>(); - std::ranges::sort(sizesOfLists, std::greater{}); - auto threeLargest = std::ranges::fold_left(sizesOfLists | std::views::take(3), 1, std::multiplies{}); - - // std::println("{}", threeLargest); - return threeLargest; + return -1; // Should not happen if graph is connected } auto main() -> int { - auto testCase = parseInput("test_input"); - if (testCase) { - constexpr int TEST_PUZZLE_TAKE = 10; + // Usage of std::string_view literal + auto puzzle = parseInput("puzzle_input"); - auto testResult = doTheThing(*testCase, TEST_PUZZLE_TAKE); - std::println("P1 Testcase result: {}", testResult); - - // auto testResultP2 = treePartTwo(*testCase); - // std::println("P2 Testcase result: {}", testResultP2); - } else { - std::print("{}\n", testCase.error()); + if (!puzzle) { + std::println(stderr, "Error: {}", puzzle.error()); + return 1; } - auto realPuzzle = parseInput("puzzle_input"); - if (realPuzzle) { - constexpr int REAL_PUZZLE_TAKE = 1000; - // auto realResult = doTheThing(*realPuzzle, REAL_PUZZLE_TAKE); - // std::println("P1 Real result: {}", realResult); + std::println("Loaded {} points.", puzzle->size()); - auto realResultP2 = doTheThing(*realPuzzle, REAL_PUZZLE_TAKE); - std::println("P2 Real result: {}", realResultP2); - } else { - std::print("{}\n", realPuzzle.error()); - } + // Part 2 Logic + auto result = solvePartTwo(*puzzle); + std::println("Part 2 Result: {}", result); return 0; -} +} \ No newline at end of file