Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 15

day15.livemd

Day 15

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/15/04/' > day04.livemd`
# When inspecting lists of numbers, use "charlists: :as_lists"
#
Mix.install([
  # Join the string so a copy of dayN to dayM doesn't destroy it.
  {:kino, "~> 0.1" <> "4.2"}
])

# Join the string so a copy of dayN to dayM doesn't destroy it.
IEx.Helpers.c("/Users/johnb/dev/2" <> "0" <> "2" <> "4adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input

Installation and Data

input_example = Kino.Input.textarea("Example Data", monospace: true)
input_puzzleInput = Kino.Input.textarea("Puzzle Input", monospace: true)
input_source_select =
  Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
data = fn ->
  (Kino.Input.read(input_source_select) == :example &amp;&amp;
     Kino.Input.read(input_example)) ||
    Kino.Input.read(input_puzzleInput)
end

Solution

defmodule Day15 do
  @robot "@"
  @wall "#"
  @floor "."
  @box "O"
  @box_left "["
  @box_right "]"
  
  def parse(text) do
    [array, moves] = text
      |> AOC.as_doublespaced_paragraphs()

    [
      AOC.as_grid(array), 
      moves
      |> String.replace("\n", "", multiline: true)
      |> String.split("", trim: true)
    ]
  end

  def delta(grid, move) do
    case move do
      "^" -> -grid.grid_height
      ">" -> 1
      "v" -> grid.grid_height
      "<" -> -1
    end
  end

  def find_next_position(grid, position, move) do
    position + delta(grid, move)
  end

  def attempt_to_push_box(grid, position, move, next_cell) do
    step = delta(grid, move)
    non_box_position = Enum.find((position + step)..(position + grid.max_dimension * step)//step, 
      fn farther_position ->
        grid[farther_position] != @box  
      end)
      # |> AOC.inspect(label: "non_box_position")

    if grid[non_box_position] == @wall do
      {grid, position}
    else
      # if empty floor, move them all and return {new-grid, next_cell}
      # (part1 could swap a smaller number, but not part2)
      new_grid = non_box_position..position//-step
        |> Enum.chunk_every(2, 1, :discard)
        |> Enum.reduce(grid, fn [to_cell, from_cell], acc ->
          put_in(acc, [to_cell], grid[from_cell])
        end)
        |> put_in([position], @floor)
      
      {new_grid, next_cell}
    end
  end

  def solve1(text) do
    [grid, moves] = parse(text)
    # AOC.display_grid(grid, "START")
    robot = AOC.grid_cells(grid)
      |> Enum.find(fn cell -> grid[cell] == @robot end)
    
    {final_grid, _final_position} = moves
      |> Enum.reduce({grid, robot}, fn move, {acc, position} ->
        next_cell = find_next_position(acc, position, move)
        {acc, position} = case acc[next_cell] do
          @wall -> {acc, position}
          @box -> attempt_to_push_box(acc, position, move, next_cell)
          @floor -> {
            acc
            |> put_in([position], @floor)
            |> put_in([next_cell], @robot),
            next_cell
          }
        end
        # AOC.display_grid(acc, move)
        {acc, position}    
      end)

    AOC.grid_cells(final_grid)
    |> Enum.filter(fn cell -> final_grid[cell] == @box end)
    |> Enum.map(fn cell -> 100 * AOC.grid_y(final_grid, cell) + AOC.grid_x(final_grid, cell) end)
    |> Enum.sum()
  end

  def scale_up(grid) do
    grid
    |> Enum.reduce(%{}, fn {k, v}, acc ->
      cond do
        is_integer(k) &amp;&amp; v in [@floor, @wall] -> 
          acc
          |> put_in([2 * k], v)
          |> put_in([2 * k + 1], v)
        is_integer(k) &amp;&amp; v == @robot -> 
          acc
          |> put_in([2 * k], @robot)
          |> put_in([2 * k + 1], @floor)
        is_integer(k) &amp;&amp; v == @box -> 
          acc
          |> put_in([2 * k], @box_left)
          |> put_in([2 * k + 1], @box_right)
        k == :last_cell -> 
          acc
          |> put_in([:last_cell], 2 * grid.grid_height * grid.grid_width - 1)          
        k == :infinite -> 
          acc
          |> put_in([:infinite], grid.infinite)
        k == :grid_width -> 
          acc
          |> put_in([:grid_width], 2 * grid.grid_width)
        true ->
          acc
          |> put_in([k], grid[k])
      end
    end)
  end

  def attempt_to_push_big_box(grid, position, move, next_cell) do
    # find the total scope of the "box" (or pyramid or whatever)
    big_box = (grid[next_cell] == @box_left &amp;&amp; 
      grid[next_cell + 1] == @box_right) &amp;&amp; [next_cell, next_cell + 1] ||
      [next_cell - 1, next_cell]

    # if move in ["<", ">"] do
    #   # push the pair kinda like in part 1
    # else
    #   # look for pyramid to push
    # end
    # big_box = case {} do
    # end
  end

  def solve2(text) do
    [grid, moves] = parse(text)
    grid = scale_up(grid)
    AOC.display_grid(grid, "SCALED")
    robot = AOC.grid_cells(grid)
      |> Enum.find(fn cell -> grid[cell] == @robot end)
    
    {final_grid, _final_position} = moves
      |> Enum.reduce({grid, robot}, fn move, {acc, position} ->
        next_cell = find_next_position(acc, position, move)
        {acc, position} = case acc[next_cell] do
          @wall -> {acc, position}
          @box_left -> attempt_to_push_big_box(acc, position, move, next_cell)
          @box_right -> attempt_to_push_big_box(acc, position, move, next_cell)
          @floor -> {
            acc
            |> put_in([position], @floor)
            |> put_in([next_cell], @robot),
            next_cell
          }
        end
        # AOC.display_grid(acc, move)
        {acc, position}    
      end)

    AOC.grid_cells(final_grid)
    |> Enum.filter(fn cell -> final_grid[cell] == @box_left end)
    |> Enum.map(fn cell -> 
      100 * AOC.grid_y(final_grid, cell) + AOC.grid_x(final_grid, cell) 
    end)
    |> Enum.sum()
  end
end

# Example:
# ########
# #..O.O.#
# ##@.O..#
# #...O..#
# #.#.O..#
# #...O..#
# #......#
# ########

# <^^>>>vv>v<<
  
data.()
|> Day15.solve1()
|> IO.inspect(label: "\n*** Part 1 solution (example: 2028 or 10092)")
# 1430439

# data.()
# |> Day15.solve2()
# |> IO.inspect(label: "\n*** Part 2 solution (example: 9021)")
#