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

Day 13: Point of Incidence

day13.livemd

Day 13: Point of Incidence

Mix.install([
  {:kino, "~> 0.12.0"}
])

Input

input = Kino.Input.textarea("Please, paste your input here:")
defmodule Day13Shared do
  def parse(input) do
    input
    |> Kino.Input.read()
    |> String.split("\n\n")
    |> Enum.map(&parse_pattern/1)
  end

  defp parse_pattern(pattern) do
    pattern
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.reduce(%{max_x: 0, max_y: 0}, fn {row, y}, map ->
      row
      |> String.split("", trim: true)
      |> Enum.with_index()
      |> Enum.reduce(map, fn {el, x}, map ->
        map
        |> Map.put({x, y}, el)
        |> Map.update(:max_x, x, &max(&1, x))
        |> Map.update(:max_y, y, &max(&1, y))
      end)
    end)
  end

  def vertical_edge(%{max_x: mx, max_y: my} = map, folding_x) do
    to_left = a_side(folding_x, mx)
    to_right = b_side(folding_x, mx)

    left =
      Enum.map(0..my, fn y ->
        Enum.reduce(to_left, [], fn {x, delta}, rocks ->
          if Map.get(map, {x, y}) == "#", do: [delta | rocks], else: rocks
        end)
        |> Enum.reverse()
      end)

    right =
      Enum.map(0..my, fn y ->
        Enum.reduce(to_right, [], fn {x, delta}, rocks ->
          if Map.get(map, {x, y}) == "#", do: [delta | rocks], else: rocks
        end)
      end)

    left == right
  end

  def horizontal_edge(%{max_x: mx, max_y: my} = map, folding_y) do
    to_top = a_side(folding_y, my)
    to_bottom = b_side(folding_y, my)

    top =
      Enum.map(0..mx, fn x ->
        Enum.reduce(to_top, [], fn {y, delta}, rocks ->
          if Map.get(map, {x, y}) == "#", do: [delta | rocks], else: rocks
        end)
        |> Enum.reverse()
      end)

    bottom =
      Enum.map(0..mx, fn x ->
        Enum.reduce(to_bottom, [], fn {y, delta}, rocks ->
          if Map.get(map, {x, y}) == "#", do: [delta | rocks], else: rocks
        end)
      end)

    top == bottom
  end

  defp a_side(coord, max) do
    side_steps = min(max - coord + 1, coord)

    (coord - side_steps)..(coord - 1)
    |> Enum.reverse()
    |> Enum.with_index()
    |> Enum.reverse()
  end

  defp b_side(coord, max) do
    side_steps = min(max - coord + 1, coord)

    coord..(coord + side_steps - 1)
    |> Enum.with_index()
  end
end

Day13Shared.parse(input)

Part 1

defmodule Day13Part1 do
  import Day13Shared, except: [parse: 1]

  def solve(patterns) do
    patterns
    |> Enum.map(fn %{max_x: mx, max_y: my} = map ->
      vert = Enum.find(1..mx, &vertical_edge(map, &1))
      hori = Enum.find(1..my, &horizontal_edge(map, &1))

      {vert, hori}
    end)
    |> IO.inspect()
    |> Enum.reduce(0, fn
      {v, nil}, acc -> acc + v
      {nil, h}, acc -> acc + h * 100
    end)
  end
end

input
|> Day13Shared.parse()
|> Day13Part1.solve()

# 29213 is the right answer

Part 2

defmodule Day13Part2 do
  import Day13Shared, except: [parse: 1]

  def solve(patterns) do
    patterns
    |> Enum.map(fn %{max_x: mx, max_y: my} = map ->
      vert = Enum.find(1..mx, &vertical_edge(map, &1))
      hori = Enum.find(1..my, &horizontal_edge(map, &1))
      vert? = is_nil(hori)

      new_vert_hori =
        map
        |> desmudge(vert?)
        |> Enum.reject(&(&1 == {vert, nil} or &1 == {nil, hori}))

      case new_vert_hori do
        [] -> {vert, hori}
        [new_v_h | _] -> new_v_h
      end
    end)
    |> Enum.reduce(0, fn
      {v, nil}, acc -> acc + v
      {nil, h}, acc -> acc + h * 100
    end)
  end

  defp desmudge(%{max_x: mx, max_y: my} = map, vert?) do
    for y <- 0..my, x <- 0..mx do
      new_map = boom(map, {x, y})

      if vert? do
        hori = Enum.find(1..my, &amp;horizontal_edge(new_map, &amp;1))

        if is_nil(hori) do
          {Enum.find(1..mx, &amp;vertical_edge(new_map, &amp;1)), nil}
        else
          {nil, hori}
        end
      else
        vert = Enum.find(1..mx, &amp;vertical_edge(new_map, &amp;1))

        if is_nil(vert) do
          {nil, Enum.find(1..my, &amp;horizontal_edge(new_map, &amp;1))}
        else
          {vert, nil}
        end
      end
    end
    |> Enum.filter(fn {v, h} -> not is_nil(v) or not is_nil(h) end)
  end

  defp boom(map, pos) do
    map
    |> Map.get_and_update(pos, fn
      "#" -> {"#", "."}
      "." -> {".", "#"}
    end)
    |> elem(1)
  end
end

input
|> Day13Shared.parse()
|> Day13Part2.solve()

# 29684 is too low