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

Advent of Code 2022

2022/day14.livemd

Advent of Code 2022

Mix.install([
  {:req, "~> 0.3.2"}
])

Day 14

input =
  "https://adventofcode.com/2022/day/14/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample = """
498,4 -> 498,6 -> 496,6
503,4 -> 502,4 -> 502,9 -> 494,9
"""
defmodule A do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      String.split(line, " -> ")
      |> Enum.map(fn point ->
        point
        |> String.split(",")
        |> Enum.map(&String.to_integer/1)
        |> List.to_tuple()
      end)
    end)
  end

  def lines_to_rocks(lines) do
    lines
    |> Enum.reduce(MapSet.new(), fn points, acc ->
      [head | tail] = points

      tail
      |> Enum.reduce({head, acc}, fn {x, y} = p, {{px, py}, all} ->
        # coordinate system is in this direction
        # 0 1 2 3 ...
        # 1
        # 2
        # 3
        # ...
        dx = px - x
        dy = py - y

        all =
          case {dx, dy} do
            {dx, 0} ->
              for x2 <- px..(px - dx), into: all, do: {x2, y}

            {0, dy} ->
              for y2 <- py..(py - dy), into: all, do: {x, y2}
          end

        {p, all}
      end)
      |> elem(1)
    end)

    # |> MapSet.to_list()
    # |> Enum.filter(& elem(&1, 1) != 9)
    # |> Enum.filter(& elem(&1, 0) != 502)
  end
end

Part 1

rocks =
  input
  |> A.parse()
  |> A.lines_to_rocks()
{_, max_y} =
  rocks
  |> MapSet.to_list()
  |> Enum.max_by(&amp;elem(&amp;1, 1))

{{min_x, _}, {max_x, _}} =
  rocks
  |> MapSet.to_list()
  |> Enum.min_max_by(&amp;elem(&amp;1, 0))

{min_x, max_x, max_y}
Stream.iterate(0, &amp;(&amp;1 + 1))
# |> Enum.take(100)
|> Enum.reduce_while(rocks, fn i, filled ->
  Stream.iterate(1, &amp;(&amp;1 + 1))
  |> Enum.reduce_while({500, 0}, fn _j, {x, y} ->
    cond do
      x < min_x || x > max_x || y > max_y -> {:halt, :forever}
      !MapSet.member?(filled, {x, y + 1}) -> {:cont, {x, y + 1}}
      !MapSet.member?(filled, {x - 1, y + 1}) -> {:cont, {x - 1, y + 1}}
      !MapSet.member?(filled, {x + 1, y + 1}) -> {:cont, {x + 1, y + 1}}
      true -> {:halt, {x, y}}
    end
  end)
  |> then(fn
    :forever -> {:halt, i}
    {_x, _y} = p -> {:cont, MapSet.put(filled, p)}
  end)
end)

Part 2

rocks =
  input
  |> A.parse()
  |> A.lines_to_rocks()
{_, max_y} =
  rocks
  |> MapSet.to_list()
  |> Enum.max_by(&amp;elem(&amp;1, 1))

{{min_x, _}, {max_x, _}} =
  rocks
  |> MapSet.to_list()
  |> Enum.min_max_by(&amp;elem(&amp;1, 0))

{min_x, max_x, max_y}
# max height max_y + 2 = 11

floor_y = max_y + 2

rocks = for x <- (min_x - floor_y)..(max_x + floor_y), into: rocks, do: {x, floor_y}
Stream.iterate(1, &amp;(&amp;1 + 1))
|> Enum.reduce_while(rocks, fn i, filled ->
  Stream.iterate(1, &amp;(&amp;1 + 1))
  |> Enum.reduce_while({500, 0}, fn _j, {x, y} ->
    cond do
      !MapSet.member?(filled, {x, y + 1}) -> {:cont, {x, y + 1}}
      !MapSet.member?(filled, {x - 1, y + 1}) -> {:cont, {x - 1, y + 1}}
      !MapSet.member?(filled, {x + 1, y + 1}) -> {:cont, {x + 1, y + 1}}
      y == 0 -> {:halt, :done}
      true -> {:halt, {x, y}}
    end
  end)
  |> then(fn
    :done -> {:halt, i}
    {_x, _y} = p -> {:cont, MapSet.put(filled, p)}
  end)
end)