Skip to content

Commit

Permalink
Part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
EduardGomezEscandell committed Dec 28, 2023
1 parent a73d0be commit 96b0a2e
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 35 deletions.
9 changes: 9 additions & 0 deletions 2023/data/10/example3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
...........
.S-------7.
.|F-----7|.
.||.....||.
.||.....||.
.|L-7.F-J|.
.|..|.|..|.
.L--J.L--J.
...........
10 changes: 10 additions & 0 deletions 2023/data/10/example4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
143 changes: 110 additions & 33 deletions 2023/solvelib/10/day10.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
#include "day10.hpp"
#include "xmaslib/functional/functional.hpp"

#include "xmaslib/log/log.hpp"
#include "xmaslib/matrix/text_matrix.hpp"

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <execution>
#include <functional>
#include <numeric>
#include <optional>
#include <stdexcept>
#include <vector>

namespace {

using Int = std::int64_t;

struct coords {
std::size_t r;
std::size_t c;
Int r;
Int c;
};

enum heading {
Expand Down Expand Up @@ -96,21 +103,21 @@ std::optional<coords> take_step(xmas::views::text_matrix map, coords pos, headin
if(pos.r == 0) return {};
return {{pos.r - 1, pos.c}};
case S:
if(pos.r+1 == map.nrows()) return {};
if(std::size_t(pos.r+1) == map.nrows()) return {};
return {{pos.r + 1, pos.c}};
case W:
if(pos.c == 0) return {};
return {{pos.r, pos.c - 1}};
case E:
if(pos.c+1 == map.ncols()) return {};
if(std::size_t(pos.c+1) == map.ncols()) return {};
return {{pos.r, pos.c + 1}};
}
// clang-format on

throw std::runtime_error("Invalid value for heading");
}

std::vector<heading> find_start_directions(xmas::views::text_matrix map, coords start) {
heading find_start_direction(xmas::views::text_matrix map, coords start) {
std::vector<heading> possible_headings;

// North side
Expand All @@ -121,61 +128,122 @@ std::vector<heading> find_start_directions(xmas::views::text_matrix map, coords
}

// South side
if (start.r + 1 != map.nrows()) {
if (std::size_t(start.r + 1) != map.nrows()) {
if (update_heading(map, {start.r + 1, start.c}, S).has_value()) {
possible_headings.push_back(S);
}
}

// East side
// West side
if (start.c != 0) {
if (update_heading(map, {start.r, start.c - 1}, W).has_value()) {
possible_headings.push_back(W);
}
}

// East side
if (std::size_t(start.c + 1) != map.ncols()) {
if (update_heading(map, {start.r, start.c + 1}, E).has_value()) {
possible_headings.push_back(E);
}
}

// West side
if (start.c != 0) {
if (update_heading(map, {start.r, start.c - 1}, W).has_value()) {
possible_headings.push_back(W);
}
switch (possible_headings.size()) {
case 0:
throw std::runtime_error("No possible heading from start point");
case 1:
throw std::runtime_error("No closed loop: only one direction available from start");
case 2:
break;
default:
xlog::warning("Multiple possible loops");
}

return possible_headings;
return possible_headings.front();
}

Int compute_perimeter(std::vector<coords> const& vertices) {
return std::transform_reduce(std::execution::par_unseq, vertices.cbegin(), vertices.cend() - 1,
vertices.cbegin() + 1, 0, std::plus<Int>{},
[](coords const& u, coords const& v) { return std::abs(u.r - v.r) + std::abs(u.c - v.c); });
}

// Shoelace formula to find the (maybe non-integer) area, so 2*area is returned.
// https://en.wikipedia.org/wiki/Shoelace_formula#Shoelace_formula
Int compute_double_area(std::vector<coords> const& vertices) {
Int twice_area = std::transform_reduce(std::execution::par_unseq, vertices.begin(),
vertices.end() - 1, vertices.begin() + 1, Int{0}, std::plus<Int>{},
[](coords const& u1, coords const& u2) { return u1.r * u2.c - u1.c * u2.r; });

// Shoelace formula returns the signed area: the sign depends on wether the polygon is traversed
// clockwise or counterclockise. This is not something we care about here, so we remove the sign.
return std::abs(twice_area);
}

std::uint64_t loop_size(xmas::views::text_matrix map, coords start, heading heading) {
std::vector<coords> loop_path(xmas::views::text_matrix map, coords start, heading heading) {
assert(map.at(start) == 'S');
std::vector<coords> vertices{start};

std::uint64_t dist = 1;
auto pos = take_step(map, start, heading);
if (!pos.has_value()) {
xlog::warning("Cancelling path after 0 steps");
return {};
}

auto head = std::make_optional(heading);

while (true) {

head = update_heading(map, *pos, *head);
if (!head.has_value()) {
auto new_heading = update_heading(map, *pos, heading);
if (!new_heading.has_value()) {
break;
}

pos = take_step(map, *pos, *head);
if (*new_heading != heading) {
heading = *new_heading;
vertices.push_back(*pos);
}

pos = take_step(map, *pos, heading);
if (!pos.has_value()) {
return 0; // Path path
return {}; // Bad path
}
++dist;
}

if (map.at(*pos) != 'S') {
// Bad path
return 0;
return {};
}

return dist / 2;
vertices.push_back(start);

return vertices;
}

Int loop_perimeter(xmas::views::text_matrix map, coords start, heading heading) {
auto vertices = loop_path(map, start, heading);
if (vertices.empty()) {
return 0; // Bad path
}

return compute_perimeter(vertices);
}

Int loop_enclosed_area(xmas::views::text_matrix map, coords start, heading heading) {
auto vertices = loop_path(map, start, heading);
if (vertices.empty()) {
return 0; // Bad path
}
vertices.push_back(vertices.front());

Int twice_area = compute_double_area(vertices);
Int perimeter = compute_perimeter(vertices);

// Pick's theorem
// A = i + b/2 - 1
//
// where A is the area, i is the count of internal points, and
// b is the count of boundary points. We can re-arrange it to:
//
// i = (2A - b)/2 + 1
return (twice_area - perimeter) / 2 + 1;
}

}
Expand All @@ -185,17 +253,26 @@ std::uint64_t Day10::part1() {

auto p = std::ranges::find(this->input, 'S') - this->input.begin();
coords S{
.r = std::size_t(p) / (lines.ncols() + 1), // +1 for the \n
.c = std::size_t(p) % (lines.ncols() + 1),
.r = Int(p) / Int(lines.ncols() + 1), // +1 for the \n
.c = Int(p) % Int(lines.ncols() + 1),
};

auto possible_headings = find_start_directions(lines, S);
auto heading = find_start_direction(lines, S);

return std::transform_reduce(std::execution::par_unseq, possible_headings.begin(),
possible_headings.end(), std::uint64_t{0}, xmas::max<std::uint64_t>{},
[&](heading h) { return loop_size(lines, S, h); });
Int perimeter = loop_perimeter(lines, S, heading);
return static_cast<std::uint64_t>(perimeter / 2);
}

std::uint64_t Day10::part2() {
throw std::runtime_error("Not implemented");
xmas::views::text_matrix lines(this->input);

auto p = std::ranges::find(this->input, 'S') - this->input.begin();
coords S{
.r = Int(p) / Int(lines.ncols() + 1), // +1 for the \n
.c = Int(p) % Int(lines.ncols() + 1),
};

auto heading = find_start_direction(lines, S);
Int enclosed_area = loop_enclosed_area(lines, S, heading);
return static_cast<std::uint64_t>(enclosed_area);
}
25 changes: 23 additions & 2 deletions 2023/solvelib/10/day10_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,35 @@ TEST_CASE("Day 10") {
Day10 solution{};
solution.set_input("./data/10/example1.txt");
solution.load();
REQUIRE_THROWS(solution.part2());
REQUIRE_EQ(solution.part2(), 1);
}

SUBCASE("Part 2, example 2") {
Day10 solution{};
solution.set_input("./data/10/example2.txt");
solution.load();
REQUIRE_EQ(solution.part2(), 1);
}

SUBCASE("Part 2, example 3") {
Day10 solution{};
solution.set_input("./data/10/example3.txt");
solution.load();
REQUIRE_EQ(solution.part2(), 4);
}

SUBCASE("Part 2, example 4") {
Day10 solution{};
solution.set_input("./data/10/example4.txt");
solution.load();
REQUIRE_EQ(solution.part2(), 10);
}

SUBCASE("Real data") {
Day10 solution{};
solution.set_input("./data/10/input.txt");
solution.load();
REQUIRE_EQ(solution.part1(), 6956);
REQUIRE_THROWS(solution.part2());
REQUIRE_EQ(solution.part2(), 455);
}
}

0 comments on commit 96b0a2e

Please sign in to comment.