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

2023 Day 10

livebooks/2023/10.livemd

2023 Day 10

Mix.install([
  {:req, "~> 0.4.5"},
  {:vega_lite, "~> 0.1.8"},
  {:kino_vega_lite, "~> 0.1.11"}
])

alias VegaLite, as: Vl

defmodule AOC do
  @aoc_session System.fetch_env!("LB_AOC_SESSION")

  def day(day, year) when day in 1..25 do
    headers = [cookie: "session=#{@aoc_session}"]
    req = Req.new(base_url: "https://adventofcode.com/#{year}/day/#{day}", headers: headers)
    input = Req.get!(req, url: "/input").body

    if input =~ "Please don't repeatedly request this endpoint before it unlocks",
      do: {:error, "Day #{day} hasn't been unlocked yet"},
      else: {:ok, %{req: req, input: input}}
  end

  def submit(answer, %{req: req}, part \\ :part_1) when part in [:part_1, :part_2] do
    part = if part == :part_2, do: 2, else: 1

    if !part_already_completed?(part, req) do
      body = Req.post!(req, url: "/answer", form: [level: part, answer: answer]).body

      cond do
        body =~ "That's the right answer" -> {:correct, answer}
        body =~ "your answer is too low" -> {:too_low, answer}
        body =~ "your answer is too high" -> {:too_high, answer}
        :else -> {:incorrect, answer}
      end
    else
      {:already_completed_part, answer}
    end
  end

  defp part_already_completed?(part, req) do
    body = Req.get!(req).body

    cond do
      body =~ "Both parts of this puzzle are complete!" -> true
      body =~ "The first half of this puzzle is complete!" -> part == 1
      :else -> false
    end
  end

  def nums(str) do
    Regex.scan(~r/\d+/, str) |> Enum.map(fn [x] -> String.to_integer(x) end)
  end
end

{:ok, day} = AOC.day(10, 2023)

Part 1

test_input_1 = """
-L|F7
7S-7|
L|7||
-L-J|
L|-JF
"""

test_input = """
7-F7-
.FJ|7
SJLL7
|F--J
LJ.LJ
"""
defmodule P1 do
  def solve(input) do
    grid = grid(input)
    {start, "S"} = Enum.find(grid, fn {{_x, _y}, ch} -> ch == "S" end)

    {steps, _path} = count_steps_in_loop(grid, start)
    div(steps, 2)
  end

  def grid(input) do
    grid =
      input
      |> String.split("\n", trim: true)
      |> Enum.map(&String.graphemes/1)

    for {r, y} <- Enum.with_index(grid),
        {ch, x} <- Enum.with_index(r),
        into: %{},
        do: {{x, y}, ch}
  end

  def count_steps_in_loop(grid, start), do: loop(grid, start, start)

  def loop(grid, start, xy, steps \\ 0, visited \\ MapSet.new()) do
    visited = MapSet.put(visited, xy)

    cond do
      l(xy) not in visited &amp;&amp; connected?(grid, xy, :left) ->
        loop(grid, start, l(xy), steps + 1, visited)

      r(xy) not in visited &amp;&amp; connected?(grid, xy, :right) ->
        loop(grid, start, r(xy), steps + 1, visited)

      u(xy) not in visited &amp;&amp; connected?(grid, xy, :up) ->
        loop(grid, start, u(xy), steps + 1, visited)

      d(xy) not in visited &amp;&amp; connected?(grid, xy, :down) ->
        loop(grid, start, d(xy), steps + 1, visited)

      :done ->
        {steps + 1, visited}
    end
  end

  def connected?(grid, xy, :left), do: grid[l(xy)] in ~w/F L -/ &amp;&amp; grid[xy] in ~w/J 7 - S/
  def connected?(grid, xy, :right), do: grid[r(xy)] in ~w/7 J -/ &amp;&amp; grid[xy] in ~w/F L - S/
  def connected?(grid, xy, :up), do: grid[u(xy)] in ~w/F 7 |/ &amp;&amp; grid[xy] in ~w/J L | S/
  def connected?(grid, xy, :down), do: grid[d(xy)] in ~w/J L |/ &amp;&amp; grid[xy] in ~w/F 7 | S/

  def l({x, y}), do: {x - 1, y}
  def r({x, y}), do: {x + 1, y}
  def u({x, y}), do: {x, y - 1}
  def d({x, y}), do: {x, y + 1}
end

# test_input_1 |> P1.solve()
day.input |> P1.solve()

Part 2

defmodule P2 do
  def solve(input) do
    grid = P1.grid(input)
    {start, "S"} = Enum.find(grid, fn {{_x, _y}, ch} -> ch == "S" end)

    {_steps, path} = P1.count_steps_in_loop(grid, start)
    Enum.max_by(path, fn {x, _} -> x end)
    {{xmin, _}, {xmax, _}} = {Enum.min(path), Enum.max(path)}

    {{_, ymin}, {_, ymax}} =
      {Enum.min_by(path, fn {_, y} -> y end), Enum.max_by(path, fn {_, y} -> y end)}

    {xmin..xmax, ymin..ymax}

    grid
    |> Enum.count(fn {_, v} -> v == "." end)
  end
end

# test_input |> P2.solve()
# |> AOC.submit(day, :part_2)
day.input |> P2.solve()

Visualization

defmodule Viz do
  def solve(input, paper) do
    grid = P1.grid(input)
    {start, "S"} = Enum.find(grid, fn {{_x, _y}, ch} -> ch == "S" end)

    count_steps_in_loop(grid, start, paper)
  end

  def count_steps_in_loop(grid, start, paper), do: loop(paper, grid, start, start)

  def loop(paper, grid, start, xy, steps \\ 0, visited \\ MapSet.new()) do
    visited = MapSet.put(visited, xy)

    {x, y} = xy
    Kino.VegaLite.push(paper, %{x: x, y: y, v: grid[xy]})
    Process.sleep(1)

    cond do
      l(xy) not in visited &amp;&amp; connected?(grid, xy, :left) ->
        loop(paper, grid, start, l(xy), steps + 1, visited)

      r(xy) not in visited &amp;&amp; connected?(grid, xy, :right) ->
        loop(paper, grid, start, r(xy), steps + 1, visited)

      u(xy) not in visited &amp;&amp; connected?(grid, xy, :up) ->
        loop(paper, grid, start, u(xy), steps + 1, visited)

      d(xy) not in visited &amp;&amp; connected?(grid, xy, :down) ->
        loop(paper, grid, start, d(xy), steps + 1, visited)

      :done ->
        {steps + 1, visited}
    end
  end

  def connected?(grid, xy, :left), do: grid[l(xy)] in ~w/F L -/ &amp;&amp; grid[xy] in ~w/J 7 - S/
  def connected?(grid, xy, :right), do: grid[r(xy)] in ~w/7 J -/ &amp;&amp; grid[xy] in ~w/F L - S/
  def connected?(grid, xy, :up), do: grid[u(xy)] in ~w/F 7 |/ &amp;&amp; grid[xy] in ~w/J L | S/
  def connected?(grid, xy, :down), do: grid[d(xy)] in ~w/J L |/ &amp;&amp; grid[xy] in ~w/F 7 | S/

  def l({x, y}), do: {x - 1, y}
  def r({x, y}), do: {x + 1, y}
  def u({x, y}), do: {x, y - 1}
  def d({x, y}), do: {x, y + 1}
end
paper =
  Vl.new(width: 600, height: 600)
  |> Vl.config(view: [stroke: :transparent])
  |> Vl.mark(:square, size: 15)
  |> Vl.encode_field(:x, "x", axis: false)
  |> Vl.encode_field(:y, "y", axis: false)
  |> Vl.encode(:color,
    value: "#039",
    condition: [test: ~s/datum["v"] == "S"/, value: "#ff6347"]
  )
  |> Kino.VegaLite.new()
  |> Kino.render()

Viz.solve(day.input, paper)
1