Day 16: The Floor Will Be Lava

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

  • What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
  • Where do I participate?: https://adventofcode.com/
  • Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
  • hades@lemm.ee
    ·
    9 months ago

    Python

    Also on Github

    53.059 line-seconds (ranks third hardest after days 8 and 12 so far).

    from .solver import Solver
    
    
    def _trace_beam(data, initial_beam_head):
      wx = len(data[0])
      wy = len(data)
      beam_heads = [initial_beam_head]
      seen_beam_heads = set()
      while beam_heads:
        next_beam_heads = []
        for x, y, dx, dy in beam_heads:
          seen_beam_heads.add((x, y, dx, dy))
          nx, ny = (x + dx), (y + dy)
          if nx < 0 or nx >= wx or ny < 0 or ny >= wy:
            continue
          obj = data[ny][nx]
          if obj == '|' and dx != 0:
            next_beam_heads.append((nx, ny, 0, 1))
            next_beam_heads.append((nx, ny, 0, -1))
          elif obj == '-' and dy != 0:
            next_beam_heads.append((nx, ny, 1, 0))
            next_beam_heads.append((nx, ny, -1, 0))
          elif obj == '/':
            next_beam_heads.append((nx, ny, -dy, -dx))
          elif obj == '\\':
            next_beam_heads.append((nx, ny, dy, dx))
          else:
            next_beam_heads.append((nx, ny, dx, dy))
        beam_heads = [x for x in next_beam_heads if x not in seen_beam_heads]
      energized = {(x, y) for x, y, _, _ in seen_beam_heads}
      return len(energized) - 1
    
    
    class Day16(Solver):
    
      def __init__(self):
        super().__init__(16)
    
      def presolve(self, input: str):
        data = input.splitlines()
        self.possible_energized_cells = (
          [_trace_beam(data, (-1, y, 1, 0)) for y in range(len(data))] +
          [_trace_beam(data, (x, -1, 0, 1)) for x in range(len(data[0]))] +
          [_trace_beam(data, (len(data[0]), y, -1, 0)) for y in range(len(data))] +
          [_trace_beam(data, (x, len(data), 0, -1)) for x in range(len(data[0]))])
    
    
      def solve_first_star(self) -> int:
        return self.possible_energized_cells[0]
    
      def solve_second_star(self) -> int:
        return max(self.possible_energized_cells)
    
  • sjmulder@lemmy.sdf.org
    ·
    9 months ago

    C

    Just tracing the ray. When it splits, recurse one way and continue the other. Didn't bother with a direction lookup table this time, just a few ifs. The ray ends when it goes out of bounds or a ray in that direction has been previously traced on a given cell (this is tracked with a separate table).

    It would've been straightforward if I hadn't gotten the 'previously visited' check wrong 😞. I was checking against the direction coming in of the tile but marking the direction going out.

    Ray function:

    static void
    ray(int x, int y, int dir)
    {
    	int c;
    
    	while (x>=0 && y>=0 && x
  • Leo Uino@lemmy.sdf.org
    ·
    9 months ago

    Haskell

    A pretty by-the-book "walk all paths" algorithm. This could be made a lot faster with some caching.

    Solution
    import Control.Monad
    import Data.Array.Unboxed (UArray)
    import qualified Data.Array.Unboxed as A
    import Data.Foldable
    import Data.Set (Set)
    import qualified Data.Set as Set
    
    type Pos = (Int, Int)
    
    readInput :: String -> UArray Pos Char
    readInput s =
      let rows = lines s
       in A.listArray ((1, 1), (length rows, length $ head rows)) $ concat rows
    
    energized :: (Pos, Pos) -> UArray Pos Char -> Set Pos
    energized start grid = go Set.empty $ Set.singleton start
      where
        go seen beams
          | Set.null beams = Set.map fst seen
          | otherwise =
              let seen' = seen `Set.union` beams
                  beams' = Set.fromList $ do
                    ((y, x), (dy, dx)) <- toList beams
                    d'@(dy', dx') <- case grid A.! (y, x) of
                      '/' -> [(-dx, -dy)]
                      '\\' -> [(dx, dy)]
                      '|' | dx /= 0 -> [(-1, 0), (1, 0)]
                      '-' | dy /= 0 -> [(0, -1), (0, 1)]
                      _ -> [(dy, dx)]
                    let p' = (y + dy', x + dx')
                        beam' = (p', d')
                    guard $ A.inRange (A.bounds grid) p'
                    guard $ beam' `Set.notMember` seen'
                    return beam'
               in go seen' beams'
    
    part1 = Set.size . energized ((1, 1), (0, 1))
    
    part2 input = maximum counts
      where
        (_, (h, w)) = A.bounds input
        starts =
          concat $
            [[((y, 1), (0, 1)), ((y, w), (0, -1))] | y <- [1 .. h]]
              ++ [[((1, x), (1, 0)), ((h, x), (-1, 0))] | x <- [1 .. w]]
        counts = map (\s -> Set.size $ energized s input) starts
    
    main = do
      input <- readInput <$> readFile "input16"
      print $ part1 input
      print $ part2 input
    

    A whopping 130.050 line-seconds!

  • cvttsd2si@programming.dev
    ·
    edit-2
    9 months ago

    Scala3

    This could be much more efficient (and quite a bit shorter), but I wanted to try out the scala-graph library (https://www.scala-graph.org)

    import day10._
    import day10.Dir._
    import scalax.collection.edges.DiEdge
    import scalax.collection.immutable.Graph
    import scalax.collection.edges.DiEdgeImplicits
    import scalax.collection.generic.AnyEdge
    import scalax.collection.generic.Edge
    
    case class Node(ps: Set[Pos])
    
    def getNode(p: Pos, d: Dir) = Node(Set(p, walk(p, d)))
    def connect(p: Pos, d1: Dir, d2: Dir) = List(getNode(p, d1) ~> getNode(p, d2), getNode(p, d2) ~> getNode(p, d1))
    
    def parseGrid(a: List[List[Char]]) = 
        def parseCell(s: Char, pos: Pos) =
            s match
                case '.' => connect(pos, Left, Right) ++ connect(pos, Up, Down)
                case '/' => connect(pos, Left, Up) ++ connect(pos, Right, Down)
                case '\\' => connect(pos, Left, Down) ++ connect(pos, Right, Up)
                case '-' => connect(pos, Left, Right) ++ List(
                    getNode(pos, Up) ~> getNode(pos, Left), getNode(pos, Up) ~> getNode(pos, Right),
                    getNode(pos, Down) ~> getNode(pos, Left), getNode(pos, Down) ~> getNode(pos, Right),
                )
                case '|' => connect(pos, Up, Down) ++ List(
                    getNode(pos, Left) ~> getNode(pos, Up), getNode(pos, Left) ~> getNode(pos, Down),
                    getNode(pos, Right) ~> getNode(pos, Up), getNode(pos, Right) ~> getNode(pos, Down),
                )
                case _ => List().ensuring(false)
            
        val edges = a.zipWithIndex.flatMap((r, y) => r.zipWithIndex.map((v, x) => v -> Pos(x, y))).map(parseCell).reduceLeft((a, b) => a ++ b)
        Graph() ++ edges
    
    def illuminationFrom(p: Pos, d: Dir, g: Graph[Node, DiEdge[Node]], inBounds: Pos => Boolean): Long =
        val es = getNode(p, d.opposite) ~> getNode(p, d)
        val g2 = g + es
        val n = g2.get(getNode(p, d))
        n.outerNodeTraverser.flatMap(_.ps).toSet.filter(inBounds).size
    
    def inBounds(a: List[String])(p: Pos) = p.x >= 0 && p.x < a(0).size && p.y >= 0 && p.y < a.size
    
    def task1(a: List[String]): Long = 
        illuminationFrom(Pos(-1, 0), Right, parseGrid(a.map(_.toList)), inBounds(a))
    
    def task2(a: List[String]): Long = 
        val inits = (for y <- a.indices yield Seq((Pos(-1, y), Right), (Pos(a(y).size, y), Left)))
            ++ (for x <- a(0).indices yield Seq((Pos(x, -1), Down), (Pos(x, a.size), Up)))
    
        val g = parseGrid(a.map(_.toList))
        inits.flatten.map((p, d) => illuminationFrom(p, d, g, inBounds(a))).max