this post was submitted on 09 Dec 2025
18 points (87.5% liked)

Advent Of Code

1217 readers
1 users here now

An unofficial home for the advent of code community on programming.dev! Other challenges are also welcome!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

Everybody Codes is another collection of programming puzzles with seasonal events.

EC 2025

AoC 2025

Solution Threads

M T W T F S S
1 2 3 4 5 6 7
8 9 10 11 12

Visualisations Megathread

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 2 years ago
MODERATORS
 

Day 9: Movie Theater

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

you are viewing a single comment's thread
view the rest of the comments
[โ€“] addie@feddit.uk 5 points 1 month ago* (last edited 1 month ago) (1 children)

C++

Correct answer in under 2 seconds, ie. about a hundred times longer than the rest of the puzzles so far combined. Can't think of a more efficient way to do this; no doubt it'll come to me at two in the morning, like usual.

Part 2 implementation breaks down the 'green tiles outline' into a series of line segments and then uses the even-odd winding rule to decide whether a point is inside or outside. Tracing the outline of the potential boxes to see whether the entirety is inside allows selection of the largest. We can skip tracing any potential box that wouldn't be bigger than what we have so far, and since the tests are easy to parallelise, have done so.

#include <atomic>
#include <boost/log/trivial.hpp>
#include <boost/unordered/concurrent_flat_map.hpp>
#include <cstddef>
#include <fstream>
#include <mutex>
#include <thread>

namespace {

struct Point {
  int x, y;
  auto operator+=(const Point &b) -> Point & {
    x += b.x;
    y += b.y;
    return *this;
  }
};

auto operator==(const Point &a, const Point &b) {
  return a.x == b.x && a.y == b.y;
}

auto operator!=(const Point &a, const Point &b) { return !(a == b); }

std::size_t hash_value(const Point &p) {
  return size_t(p.x) << 32 | size_t(p.y);
}

using Map = std::vector<Point>;
struct Line {
  Point a, b;
};

auto read() {
  auto rval = std::vector<Point>{};
  auto ih = std::ifstream{"09.txt"};
  auto line = std::string{};
  while (std::getline(ih, line)) {
    auto c1 = line.find(',');
    rval.emplace_back(
        std::stoi(line.substr(0, c1)), std::stoi(line.substr(c1 + 1))
    );
  }
  return rval;
}

auto size(const Point &a, const Point &b) -> uint64_t {
  size_t w = std::abs(a.x - b.x) + 1;
  size_t h = std::abs(a.y - b.y) + 1;
  return w * h;
}

auto part1(const std::vector<Point> &p) {
  auto maxi = std::size_t{};
  for (auto a = size_t{}; a < p.size() - 1; ++a)
    for (auto b = a + 1; b < p.size(); ++b)
      maxi = std::max(maxi, size(p[a], p[b]));
  return maxi;
}

auto make_line(const Point &a, const Point &b) {
  return Line{
      {std::min(a.x, b.x), std::min(a.y, b.y)},
      {std::max(a.x, b.x), std::max(a.y, b.y)}
  };
}

auto direction(const Point &a, const Point &b) {
  auto dx = b.x - a.x;
  auto dy = b.y - a.y;
  if (dx != 0)
    dx /= std::abs(dx);
  if (dy != 0)
    dy /= std::abs(dy);
  return Point{dx, dy};
}

// evaluates 'insideness' with the even-odd rule.  On the line -> inside
auto inside_uncached(const Point &a, const std::vector<Line> &lines) -> bool {
  auto even_odd = 0;
  for (const auto &line : lines) {
    if (line.a.y == line.b.y) { // horizontal
      if (a.y != line.a.y)
        continue;
      if (a.x <= line.a.x)
        continue;
      if (a.x < line.b.x)
        return true;
      even_odd += 1;
    } else { // vertical
      if (a.x < line.a.x)
        continue;
      if (a.y < line.a.y || a.y > line.b.y)
        continue;
      if (a.x == line.a.x)
        return true;
      even_odd += 1;
    }
  }
  return even_odd % 2 == 1;
}

using Cache = boost::unordered::concurrent_flat_map<Point, bool>;
auto cache = Cache{};

auto inside(const Point &a, const std::vector<Line> &lines) -> bool {
  std::optional<bool> o;
  bool found = cache.visit(a, [&](const auto &x) { o = x.second; });
  if (found)
    return o.value();
  auto b = inside_uncached(a, lines);
  cache.insert({a, b});
  return b;
}

auto inside(const Line &a, const std::vector<Line> &lines) -> bool {
  auto dir = direction(a.a, a.b);
  auto point = a.a;
  while (point != a.b) {
    point += dir;
    if (!inside(point, lines))
      return false;
  }
  return true;
}

auto part2(std::vector<Point> p) {
  auto outline = std::vector<Line>{};

  for (auto a = size_t{}; a < p.size() - 1; ++a)
    outline.push_back(make_line(p[a], p[a + 1]));
  outline.push_back(make_line(p.back(), p.front()));

  std::sort(outline.begin(), outline.end(), [](auto &a, auto &b) {
    return a.a.x < b.a.x;
  });

  std::sort(p.begin(), p.end(), [](auto &a, auto &b) {
    if (a.x != b.x)
      return a.x < b.x;
    return a.y > b.y;
  });

  auto maximum = std::atomic_uint64_t{};
  auto update_lock = std::mutex{};
  auto column_worker = std::atomic_uint64_t{};
  auto threadpool = std::vector<std::thread>{};

  for (auto t = size_t{}; t < std::thread::hardware_concurrency(); ++t) {
    threadpool.push_back(std::thread([&]() {
      while (true) {
        auto a = column_worker++;
        if (a > p.size() - 1)
          break;
        for (auto b = p.size() - 1; b > a; --b) {
          auto box = size(p[a], p[b]);

          // if it wouldn't be bigger, skip it
          if (box <= maximum)
            continue;

          // quick check for the opposite corners
          if (!inside(Point{p[a].x, p[b].y}, outline) ||
              !inside(Point{p[b].x, p[a].y}, outline))
            continue;

          // trace the outline
          auto left = make_line(p[a], Point{p[a].x, p[b].y});
          if (!inside(left, outline))
            continue;
          auto top = make_line(Point{p[a].x, p[b].y}, p[b]);
          if (!inside(top, outline))
            continue;
          auto right = make_line(Point{p[b].x, p[a].y}, p[b]);
          if (!inside(right, outline))
            continue;
          auto bottom = make_line(p[a], Point{p[b].x, p[a].y});
          if (!inside(bottom, outline))
            continue;

          // it's all on green tiles.  update as the biggest so far
          {
            auto _ = std::lock_guard<std::mutex>(update_lock);
            if (box > maximum)
              maximum = box;
          }
        }
      }
    }));
  }

  for (auto &thread : threadpool)
    thread.join();

  return uint64_t(maximum);
}

} // namespace

auto main() -> int {
  auto tiles = read();
  BOOST_LOG_TRIVIAL(info) << "Day 9: read " << tiles.size();
  BOOST_LOG_TRIVIAL(info) << "1: " << part1(tiles);
  BOOST_LOG_TRIVIAL(info) << "2: " << part2(tiles);
}
[โ€“] sjmulder@lemmy.sdf.org 2 points 1 month ago (1 children)

My initial attempt on even-odd got me in trouble with the outline, as given, being 1 unit wide, not zero-width. I had to compute normals to find out outline around the tiles. You write "on the line -> outside" but don't many legal rectangle candidates sit on the edge?

[โ€“] addie@feddit.uk 2 points 1 month ago (1 children)

On the line -> inside. The calculation I've done is a bit tricksy - vertical lines include the endpoints but horizontal lines exclude them, so that the even-odd rule is followed properly if you're on the same row as corners. No lines overlap.

I'd considered using the dot product to work out whether the line turned left or right, but there's a lot of cases to consider and get right. Fair play on computing the normals.

[โ€“] sjmulder@lemmy.sdf.org 1 points 1 month ago

How would you then distinguish between the situations in these two examples?

   ...#x
-> .###x
   .#xxx

   .....
-> .###.
   .#x#.