Day 8
This commit is contained in:
parent
cf51ce2dd1
commit
42d7f81e60
1 changed files with 108 additions and 134 deletions
240
day8/main.cpp
240
day8/main.cpp
|
|
@ -1,176 +1,150 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
|
||||||
#include <expected>
|
#include <expected>
|
||||||
#include <format>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <numeric>
|
||||||
#include <print>
|
#include <print>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <string_view>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using Point = std::tuple<int, int, int>;
|
// 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 {
|
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) +
|
// std::hypot is cleaner (C++17) and avoids manual pow/sqrt
|
||||||
pow(std::get<2>(a) - std::get<2>(b), 2));
|
// 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::vector<Point>, std::string> {
|
static auto parseInput(std::string_view filename) -> std::expected<std::vector<Point>, std::string> {
|
||||||
std::ifstream inputF{filename};
|
std::ifstream inputF{std::string(filename)};
|
||||||
|
|
||||||
if (!inputF) {
|
if (!inputF) {
|
||||||
return std::unexpected{"Some file open error."};
|
return std::unexpected{"Could not open file."};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Point> pointVec{};
|
std::vector<Point> points;
|
||||||
|
std::string line;
|
||||||
|
|
||||||
std::string puzzleLine;
|
while (std::getline(inputF, line)) {
|
||||||
while (std::getline(inputF, puzzleLine)) {
|
if (line.empty()) {
|
||||||
if (!puzzleLine.empty()) {
|
continue;
|
||||||
auto subParts = std::ranges::to<std::vector<std::string>>(std::views::split(puzzleLine, ','));
|
}
|
||||||
pointVec.emplace_back(std::stoi(subParts[0]), std::stoi(subParts[1]), std::stoi(subParts[2]));
|
|
||||||
|
// 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]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// std::println("pointList: {}", pointVec);
|
return points;
|
||||||
return pointVec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto getCombinations(const std::vector<Point> &pointVec) -> std::vector<std::pair<Point, Point>> {
|
static auto generateSortedEdges(const std::vector<Point> &points) -> std::vector<Edge> {
|
||||||
std::vector<std::pair<Point, Point>> result;
|
std::vector<Edge> edges;
|
||||||
|
edges.reserve((points.size() * (points.size() - 1)) / 2);
|
||||||
|
|
||||||
result.reserve(pointVec.size() * pointVec.size());
|
// Standard triangular loop is still clearer/faster than range combinatorics here
|
||||||
|
for (size_t i = 0; i < points.size(); ++i) {
|
||||||
for (size_t i = 0; i < pointVec.size(); ++i) {
|
for (size_t j = i + 1; j < points.size(); ++j) {
|
||||||
for (size_t j = i + 1; j < pointVec.size(); ++j) {
|
edges.push_back(
|
||||||
result.emplace_back(pointVec[i], pointVec[j]);
|
{.u = static_cast<int>(i), .v = static_cast<int>(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<Point> &pointVec) -> auto {
|
// --- Main Logic ---
|
||||||
auto comparisonLamb = [](const std::pair<Point, Point> &pair1, const std::pair<Point, Point> &pair2) -> bool {
|
|
||||||
return getDistance(pair1.first, pair1.second) < getDistance(pair2.first, pair2.second);
|
|
||||||
};
|
|
||||||
std::map<std::pair<Point, Point>, double, decltype(comparisonLamb)> sortedMap(comparisonLamb);
|
|
||||||
|
|
||||||
auto combinations = getCombinations(pointVec);
|
static auto solvePartTwo(const std::vector<Point> &points) -> long long {
|
||||||
for (const auto &combination : combinations) {
|
auto edges = generateSortedEdges(points);
|
||||||
|
DSU dsu(points.size());
|
||||||
|
|
||||||
// std::println("Combinations: {}:{}", combination.first, combination.second);
|
for (const auto &[u, v, dist] : edges) {
|
||||||
sortedMap[combination] = getDistance(combination.first, combination.second);
|
// Try to unite the two points
|
||||||
}
|
if (dsu.unite(u, v)) {
|
||||||
|
// If this connection reduced us to exactly 1 group, we are done!
|
||||||
return sortedMap;
|
if (dsu.groups == 1) {
|
||||||
}
|
std::println("Connected all points! Last edge: {} <-> {}", u, v);
|
||||||
|
return (long long)points[u].x * (long long)points[v].x;
|
||||||
static auto doTheThing(const std::vector<Point> &pointVec, int numberToTake) -> long long {
|
|
||||||
auto distanceMap = getDistances(pointVec);
|
|
||||||
std::map<Point, int> pointToCircuitMap{};
|
|
||||||
std::unordered_map<int, std::vector<Point>> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} 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");
|
return -1; // Should not happen if graph is connected
|
||||||
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::vector<int>>();
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto main() -> int {
|
auto main() -> int {
|
||||||
auto testCase = parseInput("test_input");
|
// Usage of std::string_view literal
|
||||||
if (testCase) {
|
auto puzzle = parseInput("puzzle_input");
|
||||||
constexpr int TEST_PUZZLE_TAKE = 10;
|
|
||||||
|
|
||||||
auto testResult = doTheThing(*testCase, TEST_PUZZLE_TAKE);
|
if (!puzzle) {
|
||||||
std::println("P1 Testcase result: {}", testResult);
|
std::println(stderr, "Error: {}", puzzle.error());
|
||||||
|
return 1;
|
||||||
// auto testResultP2 = treePartTwo(*testCase);
|
|
||||||
// std::println("P2 Testcase result: {}", testResultP2);
|
|
||||||
} else {
|
|
||||||
std::print("{}\n", testCase.error());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto realPuzzle = parseInput("puzzle_input");
|
std::println("Loaded {} points.", puzzle->size());
|
||||||
if (realPuzzle) {
|
|
||||||
constexpr int REAL_PUZZLE_TAKE = 1000;
|
|
||||||
// auto realResult = doTheThing(*realPuzzle, REAL_PUZZLE_TAKE);
|
|
||||||
// std::println("P1 Real result: {}", realResult);
|
|
||||||
|
|
||||||
auto realResultP2 = doTheThing(*realPuzzle, REAL_PUZZLE_TAKE);
|
// Part 2 Logic
|
||||||
std::println("P2 Real result: {}", realResultP2);
|
auto result = solvePartTwo(*puzzle);
|
||||||
} else {
|
std::println("Part 2 Result: {}", result);
|
||||||
std::print("{}\n", realPuzzle.error());
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue