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

  • skissue@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    10 hours ago

    Uiua

    My silly part one solution, because I have no idea how to do part two (this is usually the difficulty at which I bow out, but I want to try actually expanding my knowledge this year!).

    Parse         ← ⊜(⊜⋕⊸≠@,)⊸≠@\n
    CornerClosest ← ⊏⊢⍏⊸≡⌞(/+ⁿ2-)
    Big           ← 9999999
    RectSize      ← /×+1⌵-
    Part₁ ← (
      ∩⌟∩⌟CornerClosest 0_0 1000000_1000000 Big_0 0_Big
      ↥∩RectSize
    )
    
    $ 7,1
    $ 11,1
    $ 11,7
    $ 9,7
    $ 9,5
    $ 2,5
    $ 2,3
    $ 7,3
    
    &fras "9.txt"
    Part₁ Parse
    
  • Gobbel2000@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    10 hours ago

    Rust

    This one broke me, I couldn’t do it without looking at some of the solutions in this thread. In the end, basically all that was necessary for part 2 is to check for every candidate rectangle if:

    1. No corners (red tiles) are inside the rectangle, and
    2. No lines intersect the rectangle.

    Plus a bunch shifting numbers around by 1. The code is not pretty at all, but at least it turned very efficient, solving part 2 in just 4.3ms. I have no idea how generalized the solution is. It definitely assumes that any larger rectangle is spanned inside the area and that the path doesn’t cross over itself. Also of course that there are no two corners next to each other forming a 180 degree turn.

    View on github

    Code
    use std::ops::{Range, RangeInclusive};
    
    fn parse_input(input: &str) -> Vec<(u32, u32)> {
        input
            .lines()
            .map(|l| {
                let (a, b) = l.split_once(',').unwrap();
                (a.parse::<u32>().unwrap(), b.parse::<u32>().unwrap())
            })
            .collect()
    }
    
    #[inline]
    fn area(a: (u32, u32), b: (u32, u32)) -> u64 {
        (a.0.abs_diff(b.0) as u64 + 1) * (a.1.abs_diff(b.1) as u64 + 1)
    }
    
    fn part1(input: String) {
        let tiles = parse_input(&input);
        let mut largest = 0;
        for t1 in &tiles {
            for t2 in &tiles {
                let a = area(*t1, *t2);
                if a > largest {
                    largest = a;
                }
            }
        }
        println!("{largest}");
    }
    
    // Returns true only if t is not inside of the rectangle
    #[inline]
    fn red_allowed(t: (u32, u32), x_range: Range<u32>, y_range: Range<u32>) -> bool {
        !(t.0 > x_range.start && t.0 + 1 < x_range.end && t.1 > y_range.start && t.1 + 1 < y_range.end)
    }
    
    fn is_contained(
        a: (u32, u32),
        b: (u32, u32),
        tiles_x: &[(u32, u32)],
        tiles_y: &[(u32, u32)],
        vert_lines: &[(u32, RangeInclusive<u32>)],
        hori_lines: &[(u32, RangeInclusive<u32>)],
    ) -> bool {
        let x_range = a.0.min(b.0)..(a.0.max(b.0) + 1);
        let y_range = a.1.min(b.1)..(a.1.max(b.1) + 1);
    
        // Check that no corners (red tiles) are inside the rectangle
        let corners_ok = if x_range.end - x_range.start <= y_range.end - y_range.start {
            // Use tiles_x
            let start = match tiles_x.binary_search(&(x_range.start, 0)) {
                Ok(e) => e,
                Err(e) => e,
            };
            tiles_x
                .iter()
                .skip(start)
                .take_while(|t| t.0 < x_range.end)
                .filter(|&&t| t != a && t != b)
                .all(|t| red_allowed(*t, x_range.clone(), y_range.clone()))
        } else {
            // Use tiles_y
            let start = match tiles_y.binary_search_by_key(&(y_range.start, 0), |(x, y)| (*y, *x)) {
                Ok(e) => e,
                Err(e) => e,
            };
            tiles_y
                .iter()
                .skip(start)
                .take_while(|t| t.1 < y_range.end)
                .filter(|&&t| t != a && t != b)
                .all(|t| red_allowed(*t, x_range.clone(), y_range.clone()))
        };
        if !corners_ok {
            return false;
        }
    
        // Check that no line intersects the inside of the rectangle
        let start = match vert_lines.binary_search_by_key(&x_range.start, |(x, _)| *x) {
            Ok(e) => e,
            Err(e) => e,
        };
        for (x, line) in vert_lines
            .iter()
            .skip(start)
            .take_while(|(x, _)| *x < x_range.end)
        {
            if x_range.start < *x
                && x_range.end > *x + 1
                && (y_range.start + 1).max(*line.start()) < (y_range.end - 1).min(line.end() + 1)
            {
                return false;
            }
        }
        let start = match hori_lines.binary_search_by_key(&y_range.start, |(y, _)| *y) {
            Ok(e) => e,
            Err(e) => e,
        };
        for (y, line) in hori_lines
            .iter()
            .skip(start)
            .take_while(|(y, _)| *y < y_range.end)
        {
            if y_range.start < *y
                && y_range.end > *y + 1
                && (x_range.start + 1).max(*line.start()) < (x_range.end - 1).min(line.end() + 1)
            {
                return false;
            }
        }
        true
    }
    
    fn part2(input: String) {
        let tiles = parse_input(&input);
    
        let mut vert_lines = Vec::new();
        let mut hori_lines = Vec::new();
        let mut prev = *tiles.last().unwrap();
        for &t in &tiles {
            if t.0 == prev.0 {
                vert_lines.push((t.0, t.1.min(prev.1)..=t.1.max(prev.1)));
            } else {
                debug_assert_eq!(t.1, prev.1);
                hori_lines.push((t.1, t.0.min(prev.0)..=t.0.max(prev.0)));
            }
            prev = t;
        }
        vert_lines.sort_by_key(|(x, _)| *x);
        hori_lines.sort_by_key(|(y, _)| *y);
    
        let mut tiles_x = tiles.clone();
        tiles_x.sort();
        let mut tiles_y = tiles.clone();
        tiles_y.sort_by_key(|(x, y)| (*y, *x));
        let mut largest = 0;
        for (idx, t1) in tiles.iter().enumerate() {
            for t2 in tiles.iter().take(idx) {
                let a = area(*t1, *t2);
                if a > largest && is_contained(*t1, *t2, &tiles_x, &tiles_y, &vert_lines, &hori_lines) {
                    largest = a;
                }
            }
        }
        println!("{largest}");
    }
    
    util::aoc_main!();
    
  • Pyro@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    11 hours ago

    Python

    Part 2 is only partially optimized (runs in ~7.5s).

    The optimization I did do is to compress the grid coords by recording all red tiles and their adjacent cells only (thanks Everybody,Codes #15)
    The part which needs further optimization is my approach to getting the largest rectangle in the polygon. I did it by flood-filling the area outside the polygon, then walking through all cells of every pair of red tiles to make sure their rectangle is within the polygon.

    click to view code
    # combinations helps us get all pairs of red tiles
    # pairwise helps us get all adjacent pairs of red tiles
    from itertools import combinations, pairwise
    from collections import deque
    
    def get_area(p1, p2):
        x1, y1 = p1
        x2, y2 = p2
        return (abs(x2 - x1) + 1) * (abs(y2 - y1) + 1)
    
    @timer()
    def part1(data: str):
        red_tiles = [tuple(map(int, line.split(','))) for line in data.splitlines()]
    
        max_area = 0
        for r1, r2 in combinations(red_tiles, 2):
            max_area = max(max_area, get_area(r1, r2))
        return max_area
    
    def compress_coordinates(coords: list[tuple[int, int]]):
        i_of_interest_set = set()
        j_of_interest_set = set()
    
        for i, j in coords:
            i_of_interest_set.update([i-1, i, i+1])
            j_of_interest_set.update([j-1, j, j+1])
        
        sorted_is = sorted(list(i_of_interest_set))
        sorted_js = sorted(list(j_of_interest_set))
        m = len(sorted_is)
        n = len(sorted_js)
    
        compress_i_map = {val: id for id, val in enumerate(sorted_is)}
        compress_j_map = {val: id for id, val in enumerate(sorted_js)}
        return m, n, compress_i_map, compress_j_map
    
    
    @timer()
    def part2(data: str):
        red_tiles = [tuple(map(int, line.split(','))) for line in data.splitlines()]
        m, n, compress_i_map, compress_j_map = compress_coordinates(red_tiles)
    
        def get_compressed_coords(og_coords: tuple[int, int]):
            return compress_i_map[og_coords[0]], compress_j_map[og_coords[1]]
    
        # make the grid
        grid = [['.']*n for _ in range(m)]
        for i, j in red_tiles:
            ci, cj = get_compressed_coords((i, j))
            grid[ci][cj] = '#'
    
        # make the walls of the polygon enclosed by the red tiles
        def make_line(r1, r2):
            r1_i, r1_j = get_compressed_coords(r1)
            r2_i, r2_j = get_compressed_coords(r2)
    
            wall_char = '#'
            if r1_i == r2_i:
                # same row
                row = r1_i
                for col in range(min(r1_j, r2_j) + 1, max(r1_j, r2_j)):
                    grid[row][col] = wall_char
            elif r1_j == r2_j:
                # same col
                col = r1_j
                for row in range(min(r1_i, r2_i) + 1, max(r1_i, r2_i)):
                    grid[row][col] = wall_char
    
        for r1, r2 in pairwise(red_tiles):
            make_line(r1, r2)
        make_line(red_tiles[-1], red_tiles[0])
    
        # whiteout the exterior
        DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)]
        queue = deque([(0, 0)])
        while queue:
            i, j = queue.popleft()
            if grid[i][j] == ' ':
                continue
            grid[i][j] = ' '
    
            for di, dj in DIRECTIONS:
                ni, nj = i+di, j+dj
                if not (0 <= ni < m) or not (0 <= nj < n):
                    continue
                if grid[ni][nj] != '.':
                    continue
                queue.append((ni, nj))
        
        # DEBUG: print the grid
        # print()
        # for row in grid:
        #     print(''.join(row))
        
        # check if the rectangle formed by the corners r1, r2 is contained within the red-green polygon
        def check_contained(r1, r2):
            r1_i, r1_j = get_compressed_coords(r1)
            r2_i, r2_j = get_compressed_coords(r2)
            
            min_i, max_i = min(r1_i, r2_i), max(r1_i, r2_i)
            min_j, max_j = min(r1_j, r2_j), max(r1_j, r2_j)
    
            for i in range(min_i, max_i+1):
                for j in range(min_j, max_j+1):
                    if grid[i][j] == ' ':
                        return False
            return True
    
        # get the max area of a rectangle that is contained within the red-green polygon
        #   and has red tiles at two of its corners
        max_area = 0
        for r1, r2 in combinations(red_tiles, 2):
            area = get_area(r1, r2)
            if area <= max_area:
                # dont bother
                continue
            if not check_contained(r1, r2):
                continue
            max_area = area
    
        return max_area
    
    sample = """7,1
    11,1
    11,7
    9,7
    9,5
    2,5
    2,3
    7,3"""
    assert part1(sample) == 50
    assert part2(sample) == 24
    

    ___

  • EnEnCode@programming.dev
    link
    fedilink
    English
    arrow-up
    3
    ·
    edit-2
    13 hours ago

    Rust

    The problem today just flustered me. Sometimes I’m not convinced I’ve gotten any better at programming since 2022 (yes, I still used that exact hack of a combine_ranges for this problem) Runs on my Thinkpad P1 Gen2 in 180.9 ± 0.9 ms on external power (or 726.8 ± 2.1 ms on battery; can’t remember how I configured performance/battery saver honestly lol)

    solution
    pub fn day09(input: &str) -> (u64, u64) {
        let mut part1 = 0;
        let mut part2 = 0;
        let mut squares = Vec::new();
        for line in input.trim().lines() {
            let (x, y) = line.split_once(',').unwrap();
            squares.push([x.parse::<u64>().unwrap(), y.parse::<u64>().unwrap()]);
        }
    
        let mut chunks = squares.chunks_exact(2).peekable();
        let mut lines = Vec::new();
        let peek = chunks.peek().unwrap();
        // dir is true if scanning vertically, false if horizontally
        let dir = peek[0][1] == peek[1][1];
        if !dir {
            debug_assert_eq!(peek[0][0], peek[1][0]);
        }
    
        // Each line is a tuple where .0 is the index in the scan direction and .1 is the pair of end
        // points for a line perpendicular to the scan direction
        for line in chunks {
            lines.push((
                line[0][dir as usize],
                [line[0][!dir as usize], line[1][!dir as usize]],
            ));
        }
        lines.sort();
    
        // rot is true if field 0 being less than field 1 enters the shape, false for exiting
        let rot = lines[0].1[0] < lines[0].1[1];
    
        for idx_1 in 0..squares.len() - 1 {
            'o: for idx_2 in (idx_1 + 1)..squares.len() {
                let s1 = squares[idx_1];
                let s2 = squares[idx_2];
                let area = (s1[0].abs_diff(s2[0]) + 1) * (s1[1].abs_diff(s2[1]) + 1);
                part1 = std::cmp::max(part1, area);
    
                if area <= part2 {
                    continue;
                }
                // Think of "up" as the direction the scan line travels
                let floor = std::cmp::max(s1[dir as usize], s2[dir as usize]);
                let ceil = std::cmp::min(s1[dir as usize], s2[dir as usize]);
                let left_wall = std::cmp::min(s1[!dir as usize], s2[!dir as usize]);
                let right_wall = std::cmp::max(s1[!dir as usize], s2[!dir as usize]);
                let floor_idx = lines.binary_search_by_key(&floor, |(l, _)| *l).unwrap();
                let ceil_idx = lines.binary_search_by_key(&ceil, |(l, _)| *l).unwrap();
      
                // If a line contracts the valid range, check if that contraction intersects the walls,
                // since that necessitates uncolored tiles in the bounding box
                let skip = |line: [u64; 2]| {
                    if rot != (line[0] < line[1]) {
                        return (left_wall..right_wall).contains(&line[0])
                            || (left_wall..right_wall).contains(&line[1]);
                    }
                    false
                };
    
                for line in &lines[ceil_idx..floor_idx] {
                    if skip(line.1) {
                        continue 'o;
                    }
                }
                let mut combo_ranges = Vec::new();
                // This `rev` is needed for correctness, although the full input does not seem to care
                // It actually hurts performance a lot :(
                for line in lines[0..=ceil_idx].iter().rev() {
                    if skip(line.1) {
                        continue 'o;
                    }
                    if rot {
                        combo_ranges.push(line.1);
                    } else {
                        combo_ranges.push([line.1[1], line.1[0]]);
                    }
                    combo_ranges = combine_ranges(combo_ranges);
                    // If overlapping the target range results in no change of range, then the target
                    // range is fully covered and this rectangle is valid
                    let mut test_range = combo_ranges.clone();
                    test_range.push([left_wall, right_wall]);
                    if combo_ranges == combine_ranges(test_range) {
                        part2 = area;
                        continue 'o;
                    }
                }
            }
        }
        (part1, part2)
    }
    
  • addie@feddit.uk
    link
    fedilink
    arrow-up
    5
    ·
    edit-2
    15 hours ago

    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);
    }
    
  • CameronDev@programming.devOPM
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    10 hours ago

    Rust

    pt2: 71ms.

    Wow. What a step up in difficulty. Using the geo library got me the right answer, and quickly in terms of code, but run time was terrible (2m+). Switching back to simply checking if a wall is entirely outside the rectangle got me a much faster win, and the code isn’t too bad either.

    Code and algorithm description

    (A wall is outside if its entirely to the left OR entirely to the right of the rect) OR (entirely above OR entirely below).

       fn check_wall_outside(
            c: &(&Coord, &Coord),
            top: usize,
            bottom: usize,
            left: usize,
            right: usize,
        ) -> bool {
            if (c.0.x <= left && c.1.x <= left) || (c.0.x >= right && c.1.x >= right) {
                return true;
            }
            if (c.0.y <= top && c.1.y <= top) || (c.0.y >= bottom && c.1.y >= bottom) {
                return true;
            }
            false
        }
    
       #[test]
        fn test_y2025_day9_part2_mine1() {
            let input = include_str!("../../input/2025/day_9.txt");
            let coords = input
                .lines()
                .map(|l| {
                    let halfs = l.split_once(',').unwrap();
                    Coord {
                        x: halfs.0.parse::<usize>().unwrap(),
                        y: halfs.1.parse::<usize>().unwrap(),
                    }
                })
                .collect::<Vec<Coord>>();
    
            let mut walls = vec![];
    
            for i in 0..coords.len() {
                let first = &coords[i];
                let second = coords.get(i + 1).unwrap_or(coords.first().unwrap());
    
                walls.push((first, second));
            }
    
            let mut max_area = 0;
            for i in 0..coords.len() {
                let first = &coords[i];
                'next_rect: for j in i..coords.len() {
                    let second = &coords[j];
                    if first == second {
                        continue 'next_rect;
                    }
                    let area = (first.x.abs_diff(second.x) + 1) * (first.y.abs_diff(second.y) + 1);
                    if area < max_area {
                        continue 'next_rect;
                    }
    
                    let (top, bottom) = if first.y > second.y {
                        (second.y, first.y)
                    } else {
                        (first.y, second.y)
                    };
    
                    let (left, right) = if first.x > second.x {
                        (second.x, first.x)
                    } else {
                        (first.x, second.x)
                    };
    
                    for wall in &walls {
                        if !check_wall_outside(wall, top, bottom, left, right) {
                            continue 'next_rect;
                        }
                    }
    
                    max_area = area;
                }
            }
            assert_eq!(max_area, 1542119040);
            println!("Part 2: {}", max_area);
        }
    
  • chunkystyles@sopuli.xyz
    link
    fedilink
    English
    arrow-up
    3
    ·
    13 hours ago

    Kotlin

    My solution can’t be very good because it took over a minute to run part 2. I used a ray casting algorithm to determine if a point was in the main polygon. I did that for the 2 corners of each rectangle that weren’t given as input. If both of those corners were in the polygon, then I checked each point of each edge of that rectangle.

    My first stab at this didn’t consider the edge points of the rectangle, and after looking at some visualizations of the polygon, I could see where I was going wrong. I wouldn’t have considered a rectangle with all 4 corners in the polygon could have an edge that goes outside the polygon without looking at a visualization.

    Part 2 code:

    fun main() {
        val input = getInput(9)
        val polygon = parseInput1(input)
        val rectangles: MutableList<Rectangle> = mutableListOf()
        for (i in polygon.indices) {
            for (j in i + 1..<polygon.size) {
                rectangles.add(Rectangle(polygon[i], polygon[j]))
            }
        }
        rectangles.sortByDescending { it.area }
        var answer: Rectangle? = null
        for (rectangle in rectangles) {
            if (isInsidePolygon(rectangle.corner3, polygon)
                && isInsidePolygon(rectangle.corner4, polygon)
            ) {
                val edgePoints: MutableList<Pair<Long, Long>> = mutableListOf()
                edgePoints.addAll(getAllPointsBetween(rectangle.corner1, rectangle.corner3))
                edgePoints.addAll(getAllPointsBetween(rectangle.corner1, rectangle.corner4))
                edgePoints.addAll(getAllPointsBetween(rectangle.corner2, rectangle.corner3))
                edgePoints.addAll(getAllPointsBetween(rectangle.corner2, rectangle.corner4))
                var isInside = true
                for (edgePoint in edgePoints) {
                    if (!isInsidePolygon(edgePoint, polygon)) {
                        isInside = false
                        break
                    }
                }
                if (isInside) {
                    answer = rectangle
                    break
                }
            }
        }
        println(answer?.area ?: "Whoops")
    }
    
    fun parseInput1(input: String): List<Pair<Long, Long>> = input.lines()
        .filter { it.isNotBlank() }
        .map {
            val split = it.split(",")
            split[0].toLong() to split[1].toLong()
        }
    
    data class Rectangle(val corner1: Pair<Long, Long>, val corner2: Pair<Long, Long>) {
        val corner3: Pair<Long, Long> = corner1.first to corner2.second
        val corner4: Pair<Long, Long> = corner2.first to corner1.second
        val area: Long = (1 + abs(corner1.first - corner2.first)) * (1 + abs(corner1.second - corner2.second))
    }
    
    fun isInsidePolygon(point: Pair<Long, Long>, polygon: List<Pair<Long, Long>>): Boolean {
        val x = point.first
        val y = point.second
        var intersectionCount = 0L
        for (i in polygon.indices) {
            val p1 = polygon[i]
            val p2 = polygon[(i + 1) % polygon.size]
            if (point == p1 || point == p2 || isOnEdge(point, p1, p2)) {
                return true
            }
            val y1 = p1.second
            val y2 = p2.second
            val crossesRay = (y in y1..<y2) || (y in y2..<y1)
            if (crossesRay) {
                val x1 = p1.first
                val x2 = p2.first
                val xIntersect = (x2 - x1) * (y - y1).toDouble() / (y2 - y1) + x1
                if (x < xIntersect) {
                    intersectionCount++
                }
            }
        }
        return intersectionCount % 2L != 0L
    }
    
    fun isOnEdge(p: Pair<Long, Long>, p1: Pair<Long, Long>, p2: Pair<Long, Long>): Boolean {
        val crossProduct = (p.second - p1.second) * (p2.first - p1.first) -
                (p.first - p1.first) * (p2.second - p1.second)
        if (crossProduct != 0L) {
            return false
        }
        val isBetweenX = p.first in minOf(p1.first, p2.first)..maxOf(p1.first, p2.first)
        val isBetweenY = p.second in minOf(p1.second, p2.second)..maxOf(p1.second, p2.second)
        return isBetweenX && isBetweenY
    }
    
    fun getAllPointsBetween(left: Pair<Long, Long>, right: Pair<Long, Long>): List<Pair<Long, Long>> {
        if (right.first == left.first) {
            val max = maxOf(left.second, right.second)
            val min = minOf(left.second, right.second)
            return (min + 1..<max).toList().map { right.first to it }
        } else if (right.second == left.second) {
            val max = maxOf(left.first, right.first)
            val min = minOf(left.first, right.first)
            return (min + 1..<max).toList().map { it to right.second }
        } else {
            throw Exception("Whoops")
        }
    }
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    3
    ·
    18 hours ago

    Futhark

    First, formatting the input with an unreadable sed script:

    formatting script
    1i [
    1,$ {
    	s/^/[/
    	s/$/], /
    }
    $i ]
    $d
    

    Then, the actual program. main is the default entrypoint, part one is trivially solved in the preparations for part two. In part two, the faster check is to look for any point inside the current rectangle. If this can’t find any, it’ll have to check whether any edge crosses through the rectangle with a simple range check. I’m not happy with the performance, I feel like I left a lot on the table.

    As always, wonky syntax highlighting
    import "lib/github.com/diku-dk/sorts/radix_sort"
    
    def (&&&) 'a 'b 'c (f: a -> b) (g: a -> c) (x: a): (b, c) = (f x, g x)
    def odd (x: i64): bool = x % 2 == 1
    
    def count 'a (f: a -> bool) (xs: []a): i64
      = map (f >-> i64.bool) xs |> reduce_comm (+) 0
    
    def coordinateFromArray (as: [2]i64): (i64, i64)
      = (as[0], as[1])
    
    def maximum = reduce_comm i64.max i64.lowest
    def minimum = reduce_comm i64.min i64.highest
    
    def concatMap [n] 'a 'b (f: a -> ?[l].[l]b) (placeholder: b) (xs: [n]a): *[]b
      = let totalLength = reduce (+) 0 <| map (\ x -> length (f x)) xs in
        ( loop (results, offset) = (replicate totalLength placeholder, 0)
          for x in xs
          do
            let bs = f x in
            let scatterIndices = indices bs |> map (+offset) in
            (scatter results scatterIndices bs, offset + length bs)
        ).0
    
    def rectSize (a: (i64, i64)) (b: (i64, i64)) = 
      let dx = i64.max a.0 b.0 - i64.min a.0 b.0 in
      let dy = i64.max a.1 b.1 - i64.min a.1 b.1 in
      (dx + 1) * (dy + 1)
    
    def pair_iota (n: i64): [n](i64, i64)
      = map (\ j -> (n, j)) (iota n)
    
    def pairs 'a (xs: []a): [](a, a)
      = concatMap pair_iota (i64.highest, i64.highest) (indices xs)
        |> map (\ (i, j) -> (xs[i], xs[j]))
    
    def findFirst 'a (f: a -> bool) (xs: []a): a
      = ( loop (i, x) = (0, xs[0])
          while not (f x)
          do (i + 1, xs[i+1])
        ) |> (.1)
    
    def orderedPair (p: (i64, i64)): (i64, i64) = (i64.min p.0 p.1, i64.max p.0 p.1)
    
    def overlapsWith (a: (i64, i64)) (b: (i64, i64)): bool 
      = a.0 < b.1 && b.0 < a.1
    
    def anyInside (points: [](i64, i64)) (rectangle: (((i64, i64), (i64, i64)), i64))
      = let (lowerX, upperX) = orderedPair (rectangle.0.0.0, rectangle.0.1.0) in
        let (lowerY, upperY) = orderedPair (rectangle.0.0.1, rectangle.0.1.1) in
        map (\ (x, y) -> lowerX < x && x < upperX && lowerY < y && y < upperY) points
        |> or
    
    def anyIntersects (edges: []((i64, i64), (i64, i64))) (rectangle: (((i64, i64), (i64, i64)), i64)): bool
      = let rectRangeX = orderedPair (rectangle.0.0.0, rectangle.0.1.0) in
        let rectRangeY = orderedPair (rectangle.0.0.1, rectangle.0.1.1) in
        map (\ e -> 
          let edgeRangeX = orderedPair (e.0.0, e.1.0) in
          let edgeRangeY = orderedPair (e.0.1, e.1.1) in
          (edgeRangeX `overlapsWith` rectRangeX) && (edgeRangeY `overlapsWith` rectRangeY)
        ) edges
        |> or
    
    def part2 (sortedRectangles: [](((i64, i64), (i64, i64)), i64)) (points: [](i64, i64))
      = let edges = zip points (rotate 1 points) in
        let filled = \ r -> not (anyInside points r || anyIntersects edges r) in
        findFirst filled sortedRectangles
        |> (.1)
    
    -- benchmark
    -- ==
    -- input @fut-input
    -- auto output
    
    def main (coordinateArrays: [][2]i64)
      = let coordinates = map coordinateFromArray coordinateArrays in
        let rectangleCorners = pairs coordinates in
        let rectangleSizes = map (id &&& uncurry rectSize) rectangleCorners in
        let sortedRectangles = radix_sort_by_key (.1) i64.num_bits i64.get_bit rectangleSizes |> reverse in
      (sortedRectangles[0].1, part2 sortedRectangles coordinates)
    
  • Camille@lemmy.ml
    link
    fedilink
    arrow-up
    3
    ·
    18 hours ago

    Go

    Oh my god. I’m calling: tomorrow will probably be the day I fail lmao. It has been so hard to get a correct result. Then I spent a solid half hour to optimize the solution so that :

    • it terminates
    • it doesn’t need 600GB of RAM

    It seems that using a int8 to encode color instead of the default int64 was the right idea lmao. I’m also happy to have understood how to multithread a Go application and I’m totally bought by the simplicity. This language really helped me by abstracting everything in goroutines and call it a day.

    I have also been able to create small goroutines which job was to generate values one by one and abstract the real control-flow in order not to allocate a whole big array at once, reducing further my memory footprint.

    This is the second time in my life I want to say I loved using Go. Last time was when I waited to pretty print json files, my colleague told me how to write a wannabe-oneliner in Go to do it myself. Never looked back.

    day09.go
    package main
    
    import (
    	"aoc/utils"
    	"math"
    	"slices"
    	"strconv"
    	"strings"
    	"sync"
    )
    
    type point struct {
    	x, y int
    }
    
    func (p point) areaUntil(other point) int {
    	dx := other.x - p.x
    	if dx < 0 {
    		dx = -dx
    	}
    	dy := other.y - p.y
    	if dy < 0 {
    		dy = -dy
    	}
    
    	return (dx + 1) * (dy + 1)
    }
    
    func parsePoint(line string) point {
    	parts := strings.Split(line, ",")
    	x, _ := strconv.Atoi(parts[0])
    	y, _ := strconv.Atoi(parts[1])
    	return point{x, y}
    }
    
    func getPointChannel(input chan string) chan point {
    	ch := make(chan point, cap(input))
    
    	go func() {
    		for line := range input {
    			ch <- parsePoint(line)
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    func stepOne(input chan string) (int, error) {
    	points := []point{}
    	maxArea := math.MinInt
    
    	for p := range getPointChannel(input) {
    		points = append(points, p)
    
    		if len(points) == 1 {
    			continue
    		}
    
    		areas := make([]int, len(points))
    		for idx, p2 := range points {
    			areas[idx] = p.areaUntil(p2)
    		}
    
    		max := slices.Max(areas)
    		if max > maxArea {
    			maxArea = max
    		}
    	}
    
    	return maxArea, nil
    }
    
    type color int8
    
    const (
    	white color = iota
    	green
    	red
    )
    
    type matrix struct {
    	tiles  [][]color
    	points []point
    }
    
    func min(x, y int) int {
    	if x < y {
    		return x
    	} else {
    		return y
    	}
    }
    
    func max(x, y int) int {
    	if x > y {
    		return x
    	} else {
    		return y
    	}
    }
    
    func (m *matrix) squareContainsWhiteTiles(sq segment) bool {
    	xMin := min(sq[0].x, sq[1].x)
    	yMin := min(sq[0].y, sq[1].y)
    	xMax := max(sq[0].x, sq[1].x)
    	yMax := max(sq[0].y, sq[1].y)
    
    	for y := yMin; y < yMax; y++ {
    		if m.tiles[y][xMin] == white || m.tiles[y][xMax] == white {
    			return true
    		}
    	}
    
    	for x := xMin; x < xMax; x++ {
    		if m.tiles[yMin][x] == white || m.tiles[yMax][x] == white {
    			return true
    		}
    	}
    
    	return false
    }
    
    func (m *matrix) addReds() {
    	for _, p := range m.points {
    		m.addRed(p)
    	}
    }
    
    func newMatrix(points []point) (m matrix) {
    	coords := getMatrixSize(points)
    	rows := coords[1] + 1
    	cols := coords[0] + 1
    	array := make([][]color, rows)
    	for idx := range array {
    		array[idx] = make([]color, cols)
    	}
    	m = matrix{
    		tiles:  array,
    		points: points,
    	}
    
    	return m
    }
    
    func (m *matrix) addRed(p point) {
    	m.tiles[p.y][p.x] = red
    }
    
    func (m *matrix) makeBorders() {
    	for i := 0; i < len(m.points)-1; i++ {
    		m.makeBorder(m.points[i], m.points[i+1])
    	}
    	m.makeBorder(m.points[len(m.points)-1], m.points[0])
    }
    
    func (m *matrix) makeBorder(p1 point, p2 point) {
    	if p1.x == p2.x {
    		m.makeVerticalBorder(p1.x, p1.y, p2.y)
    	} else {
    		m.makeHorizontalBorder(p1.x, p2.x, p1.y)
    	}
    }
    
    func (m *matrix) makeHorizontalBorder(x1, x2, y int) {
    	if x1 > x2 {
    		tmp := x1
    		x1 = x2
    		x2 = tmp
    	}
    
    	for i := x1; i <= x2; i++ {
    		if m.tiles[y][i] == white {
    			m.tiles[y][i] = green
    		}
    	}
    }
    
    func (m *matrix) makeVerticalBorder(x, y1, y2 int) {
    	if y1 > y2 {
    		tmp := y1
    		y1 = y2
    		y2 = tmp
    	}
    
    	for i := y1; i <= y2; i++ {
    		if m.tiles[i][x] == white {
    			m.tiles[i][x] = green
    		}
    	}
    }
    
    func (m *matrix) makeGreens() {
    	m.makeBorders()
    
    	iterCount := len(m.tiles) / MagicThreadCount
    
    	parallel(func(thrId int) {
    		for i := range iterCount {
    			row := m.tiles[iterCount*thrId+i]
    			inside := false
    			lastWasWhite := false
    
    			for idx, cell := range row {
    				switch cell {
    				case white:
    					if inside {
    						row[idx] = green
    					}
    				default:
    					if lastWasWhite {
    						inside = !inside
    					}
    				}
    				lastWasWhite = cell == white
    			}
    		}
    	})
    }
    
    func getMatrixSize(points []point) [2]int {
    	var x, y int
    	for _, p := range points {
    		if p.x > x {
    			x = p.x
    		}
    		if p.y > y {
    			y = p.y
    		}
    	}
    	return [2]int{x, y}
    }
    
    func (m *matrix) String() string {
    	iterCount := len(m.points) / MagicThreadCount
    	lines := make([]string, len(m.tiles))
    
    	parallel(func(thrId int) {
    		for i := range iterCount {
    			row := m.tiles[iterCount*thrId+i]
    			runes := make([]rune, len(row))
    			for idx, col := range row {
    				switch col {
    				case white:
    					runes[idx] = '.'
    				case green:
    					runes[idx] = 'X'
    				case red:
    					runes[idx] = '#'
    				}
    			}
    			lines[iterCount*thrId+i] = string(runes)
    		}
    	})
    
    	return strings.Join(lines, "\n")
    }
    
    type segment [2]point
    
    func newSegment(p1, p2 point) segment {
    	seg := [2]point{p1, p2}
    	return seg
    }
    
    func (m *matrix) allValidSquaresWith(p1 point) chan segment {
    	ch := make(chan segment)
    
    	go func() {
    		for _, p2 := range m.points {
    			seg := newSegment(p1, p2)
    			if p2 != p1 {
    				ch <- seg
    			}
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    // 495 == 55 * 9
    // 495 == 99 * 5
    // 495 == 165 * 3
    var MagicThreadCount = 9
    
    func parallel(f func(int)) {
    	var wg sync.WaitGroup
    	for thrId := range MagicThreadCount {
    		wg.Go(func() {
    			f(thrId)
    		})
    	}
    
    	wg.Wait()
    }
    
    func stepTwo(input chan string) (int, error) {
    	points := []point{}
    	for p := range getPointChannel(input) {
    		points = append(points, p)
    	}
    	m := newMatrix(points)
    	m.addReds()
    	m.makeGreens()
    
    	iterCount := len(m.points) / MagicThreadCount
    	boys := make([]int, MagicThreadCount)
    
    	parallel(func(thrId int) {
    		largestBoy := math.MinInt
    		for i := range iterCount {
    			p := m.points[iterCount*thrId+i]
    
    			squareCh := m.allValidSquaresWith(p)
    			for sq := range squareCh {
    				area := sq[0].areaUntil(sq[1])
    				if area > largestBoy && !m.squareContainsWhiteTiles(sq) {
    					largestBoy = area
    				}
    			}
    		}
    		boys[thrId] = largestBoy
    	})
    
    	return slices.Max(boys), nil
    }
    
    func main() {
    	input := utils.FilePath("day09.txt")
    	utils.RunStep(utils.ONE, input, stepOne)
    	utils.RunStep(utils.TWO, input, stepTwo)
    }
    
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    5
    ·
    edit-2
    17 hours ago

    Uiua

    Part 1 was easy, part 2 is …less so…

    a hint that might help you

    visualising the data reveals some interesting patterns. Follow link to do so.

    Any way, here’s my Part 1 while I’m still thinking about this.

    # AOC 2025 Day 09
    # Experimental!
    D ← &fras"AOC2025day09.txt"  # Drop your file here and edit name
    /↥/×+1⌵⍉/-⍉₂⧅<2⋕°csv D       # Part 1 
    ∧⍜⊡⋅1⟜(˜↯0+1/↥)⍜⍉≡⍜⍆⊛⋕°csv D # Visualised data
    

    Part 2

    This is basically me thinking out loud, so it’s untidy, brutal, repetitive and slow. (20s in the pad) link if you care

    # Experimental!
    # AOC 2025 Day 09
    D     ← "7,1\n11,1\n11,7\n9,7\n9,5\n2,5\n2,3\n7,3"
    D     ← &fras"AOC2025day09.txt" # Drop your file here and edit name
    Parse ← ⋕°csv
    Part₁ ← /↥/×+1⌵⍉/-⍉₂⧅<2
    
    Squeeze ← ⍜⍉≡⍜⍆⊛          # Squeeze (add a sort to inspect)
    Draw    ← ∧⍜⊡⋅1⟜(˜↯0+1/↥) # Draw
    
    HLines ← (
      Squeeze Parse D
      ⍆⊕(□⍆)⊛⊸≡⊢
      ∧◇(⍜⊡˜⍜(⊏|˙=)⊙˜∘⊃(⊢⊢|↘⊙⇡°⊟+0_1⊣⍉))
    )
    
    VLines ← (
      Squeeze Parse D
      ⍆⊕(□⍆)⊛⊸≡⊣
      ∧◇(⍜⊡˜⍜(⊏|˙=)⊙˜∘⊃(⊣⊣|↘⊙⇡°⊟+0_1⊢⍉))
    )
    DrawBorder ← ⍜⍉VLines HLines ˜↯0↯2+1/↥♭Squeeze Parse D # Draws full border
    
    # DrawBorder Squeeze # running this shows these as key boundary points -> [219 121] [219 123]
    # ⊏≡⌟˜⨂[[219 121] [219 123]]⊸Squeeze # which map to -> [[94985 48652][94985 50114]]
    # leftmost -> only look left, rightmost-> only look right
    # SO, now fill the shape to make this easier.
    Fill ← (
      ⊙⊙1           # fill colour.
      Good ← =0⊙◌⊡⊢ # Needs filling. Simple edges-check.
      # Take first of list. If needs fill, do so and then add
      # its four neighbours into queue. Repeat until queue is empty.
      ⍢(⨬(↘1|◴⊂+A₂¤°⊂ ⊃(⋅∘|∘|⋅⋅⋅∘)◡(⍜(⊡|⋅∘)⊢))◡Good
      | >0⧻)
      ⋅⊙⋅
    )
    DrawBorder     # Comment out everything below here to view the boundary.
    Fill [219_120] # Now fill that boundary ([2_1] for test)
    Squeeze Parse D
    # ([0 1] for test)
    [219 123] #  [219 121] gave the wrong answer, so it's this.
    ≡⌞⊟       # couple this with every other point
    # Extract the covered window of the array for each pair of corners.
    # (Very probably doing this the hard way:-()
    # Only keep those that are all 1.
    ▽⤚≡⌟(=1/×♭≡⌞⊏⊙⊏˜∘∩(↘⊙⇡°⊟+0_1⍆)°⊟⍉)
    # Pick out our squeezed indices
    ⨂Squeeze Parse D
    # Find out what the unsqueezed values were
    ˜⊏Parse D
    # Find areas, return max.
    /↥≡/×+1⌵≡/-
    
  • Zikeji@programming.dev
    link
    fedilink
    English
    arrow-up
    5
    ·
    1 day ago

    Javascript

    I generally like trying the brute force approach first (though in the latter days it just wastes time). After exhausting 32GB of RAM I changed approaches.

    Admittedly I don’t really understand these algorithms as well as I would like, and I also will admit I inadvertently was LLM assisted :(. When I was trying to figure out what algo I should use and the AI summary in Google’s search spoiled me. But I did learn alot.

    One interesting observation, unrelated to the solution itself, is the solutions runs about 30% faster in Firefox than it does via Node (25.2.1 and 24.11.1) on my machine.

    Code
    const input = require('fs').readFileSync('input-day9.txt', 'utf-8');
    
    /** @type {Array<[number, number]} */
    const points = input.split("\n").map(r => r.split(',').map(v => parseInt(v, 10)));
    
    let largest = 0;
    let largestInside = 0;
    for (const [startX, startY] of points) {
        for (const [nextX, nextY] of points) {
            if (startX === nextX && startY === nextY) continue;
    
            const minX = Math.min(startX, nextX);
            const maxX = Math.max(startX, nextX);
            const minY = Math.min(startY, nextY);
            const maxY = Math.max(startY, nextY);
    
            const area = (maxX - minX + 1) * (maxY - minY + 1);
            if (area > largest) {
                largest = area;
            }
    
            if (area <= largestInside) continue;
    
            // center point check, ala https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
            const centerX = (minX + maxX) / 2;
            const centerY = (minY + maxY) / 2;
            let inside = false;
            for (const [i, [aX, aY]] of points.entries()) {
                const [bX, bY] = points[i - 1] ?? points[points.length - 1];
                if (centerX === aX && centerY === aY) {
                    inside = true;
                    break;
                }
                if ((aY > centerY) !== (bY > centerY)) {
                    const slope = (centerX - aX) * (bY - aY) - (bX - aX) * (centerY - aY);
                    if (slope === 0) {
                        inside = true;
                        break;
                    }
                    if ((slope < 0) !== (bY < aY)) {
                        inside = !inside;
                    }
                }
            }
    
            if (!inside) continue;
    
            // check for edge intersections, ala https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
            let intersects = false;
            for (const [i, [aX, aY]] of points.entries()) {
                const [bX, bY] = points[i - 1] ?? points[points.length - 1];
    
                if (aX === bX) {
                    if (aX > minX && aX < maxX) {
                        const wallMinY = Math.min(aY, bY);
                        const wallMaxY = Math.max(aY, bY);
    
                        if (Math.max(minY, wallMinY) < Math.min(maxY, wallMaxY)) {
                            intersects = true;
                            break;
                        }
                    }
                } else if (aY === bY) {
                    if (aY > minY && aY < maxY) {
                        const wallMinX = Math.min(aX, bX);
                        const wallMaxX = Math.max(aX, bX);
    
                        if (Math.max(minX, wallMinX) < Math.min(maxX, wallMaxX)) {
                            intersects = true;
                            break;
                        }
                    }
                }
            }
    
            if (intersects) continue;
    
            if (area > largestInside) {
                largestInside = area;
            }
        }
    }
    
    console.log(`Part 1 Answer: ${largest}`);
    console.log(`Part 2 Answer: ${largestInside}`);
    
    • Deebster@programming.dev
      link
      fedilink
      arrow-up
      3
      ·
      1 day ago

      That is surprising about Firefox - you’d have thought something that literally just runs JavaScript would be able to beat Firefox with all the UI, sandboxing, etc. 30% is a huge margin.

  • Avicenna@programming.dev
    link
    fedilink
    arrow-up
    3
    arrow-down
    1
    ·
    edit-2
    21 hours ago

    Yes I used a polygon library so what… I did eyeball the shape and guessed what the result should have been like but still more pleasing to write something that applies generally. Although I did put an area threshold (eyeballed) which subsets the corners to test, it only reduces run time to about 1/2 (from ~10s to ~5s) so that is not necessarily very critical. If I hadn’t used this library, I would probably do sth along the lines of defining my own rectangle classes with unions, intersections etc so wouldn’t be a more clever approach but much more time consuming.

    from pathlib import Path
    
    import numpy as np
    import shapely
    
    cwd = Path(__file__).parent
    
    def parse_input(file_path):
      with file_path.open("r") as fp:
        data = list(map(lambda x: list(map(int,x.split(','))), fp.readlines()))
    
      return np.array(data)
    
    
    def construct_shapes(coordinates, threshold):
    
      Itriu = np.triu_indices(coordinates.shape[0], k=2)
      squares = []
    
      for i0,i1 in zip(*Itriu):
    
        c0 = tuple(coordinates[i0,:])
        c1 = tuple(coordinates[i1,:])
        area = np.prod(abs(np.array(c0) - np.array(c1)) + np.array([1,1]))
    
        if area>threshold:
          c2 = (c0[0],c1[1])
          c3 = (c1[0],c0[1])
          squares.append(shapely.Polygon((c0,c3,c1,c2)))
    
      polygon = shapely.Polygon(coordinates)
    
      return polygon, squares
    
    
    def solve_problem(file_name, redgreen=False, threshold=0):
    
      coordinates = parse_input(Path(cwd, file_name))
    
      if not redgreen:
        areas = np.prod(abs(coordinates[None,:] - coordinates[:,None]) +\
                        np.array([1,1])[None,None,:], axis=-1)
        max_area = np.max(areas)
    
      else:
        polygon, squares = construct_shapes(coordinates, threshold)
        max_area = -np.inf
    
        for inds,square in enumerate(squares):
          if square.area==0:
            continue
    
          if polygon.contains(square):
            c = np.array(list(zip(*square.exterior.coords.xy)))
            if (a:=np.prod(abs(c[0] - c[2]) + np.array([1,1])))>max_area:
              max_area = a
    
      return int(max_area)
    
    
    
    if __name__ == "__main__":
    
      assert solve_problem("test_input") == 50
      assert solve_problem("input") == 4763509452
      assert solve_problem("test_input", True, 1) == 24
      assert solve_problem("input", True, 15000*80000) == 1516897893 # threshold by eyeballing the shape
    
    • CameronDev@programming.devOPM
      link
      fedilink
      arrow-up
      4
      ·
      19 hours ago

      Even with using geo in rust, i am still struggling, so no shame in using the library. I did get the solve in 2m32s, but I dont feel like this is optimal.

      Surely there is a better solution somewhere.

  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    4
    ·
    edit-2
    1 day ago

    Haskell

    This is pretty ugly. I got rather fed up after trying out various heuristics when the test case passed but actual data didn’t.

    import Control.Arrow  
    import Data.Function  
    import Data.Ix  
    import Data.List  
    import Data.Ord  
    
    readInput :: String -> [(Int, Int)]  
    readInput = map ((read *** (read . tail)) . break (== ',')) . lines  
    
    pairs = concatMap (\(x : xs) -> map (x,) xs) . init . tails  
    
    toRange ((a, b), (c, d)) = ((min a c, min b d), (max a c, max b d))  
    
    onTiles loop rect = cornersInside && not crossingEdges  
      where  
        cornersInside =  
          let ((a, b), (c, d)) = rect  
           in inside (a, d) && inside (c, b)  
        verticalEdges = sortOn (Down . fst . fst) $ filter (uncurry ((==) `on` fst)) loop  
        inside (x, y) =  
          let intersecting ((a, b), (_, d)) = a <= x && inRange (min b d, max b d) y  
           in maybe False (uncurry ((>) `on` snd)) $ find intersecting verticalEdges  
        crossingEdges =  
          let ((a, b), (c, d)) = rect  
           in any (crossingLoop . toRange) $  
                [ ((a, b), (c, b)),  
                  ((c, b), (c, d)),  
                  ((c, d), (a, d)),  
                  ((a, d), (a, b))  
                ]  
        crossingLoop ((a, b), (c, d))  
          | a == c = anyEdge (\((e, f), (g, h)) -> f == h && f > b && f < d && g > a && e < c)  
          | b == d = anyEdge (\((e, f), (g, h)) -> e == g && e > a && e < c && h > b && f < d)  
        anyEdge = flip any $ map toRange loop  
    
    main = do  
      input <- readInput <$> readFile "input09"  
      let rects = pairs input  
          loop = zip (last input : input) input  
          go = print . maximum . map (rangeSize . toRange)  
      go rects  
      go $ filter (onTiles loop) rects