Advent of Code 2023 - Day 13

Hey, coding champions! Ukrainian Erlanger here :metal:, welcoming you to a wonderful Wednesday. It’s Day 13 of the captivating Advent of Code, and a brand-new puzzle is awaiting your coding expertise. Embrace the challenge, tackle the problem, and don’t forget to showcase your brilliant solutions in the comments below. Let’s keep the collaborative coding spirit alive and make this Wednesday a day of triumph! Happy coding, folks! :blush:

4 Likes

Today went better than yesterday for sure :slight_smile:

defmodule Aoc.Thirteen do
  def parse(string) do
    string
    |> String.split("\n\n", trim: true)
    |> Enum.map(fn block ->
      block
      |> String.split("\n", trim: true)
      |> Enum.map(fn line ->
        line
        |> String.graphemes()
      end)
      |> load()
    end)
  end

  def load(data) do
    for {line, y} <- Enum.with_index(data, 1), {i, x} <- Enum.with_index(line, 1), into: %{} do
      {{x, y}, i}
    end
  end

  def part_one(input) do
    input
    |> Enum.map(&find_reflection/1)
    |> Enum.map(&reflection_value/1)
    |> Enum.sum()
  end

  def part_two(input) do
    input
    |> Enum.map(&smudge_and_reflect/1)
    |> Enum.map(&reflection_value/1)
    |> Enum.sum()
  end

  defp find_reflection(map) do
    {max_x, max_y} = map |> Map.keys() |> Enum.max()

    cols =
      for x <- 1..(max_x - 1), is_reflection_col?(x, max_x, max_y, map) do
        x
      end

    rows =
      for y <- 1..(max_y - 1), is_reflection_row?(y, max_x, max_y, map) do
        y
      end

    {cols, rows}
  end

  defp is_reflection_col?(x, max_x, max_y, map) do
    {l_range, r_range} = trim(x..1, (x + 1)..max_x)
    left = Enum.map(l_range, &scan_column(&1, max_y, map))
    right = Enum.map(r_range, &scan_column(&1, max_y, map))
    left == right
  end

  defp is_reflection_row?(y, max_x, max_y, map) do
    {l_range, r_range} = trim(y..1, (y + 1)..max_y)
    left = Enum.map(l_range, &scan_row(&1, max_x, map))
    right = Enum.map(r_range, &scan_row(&1, max_x, map))
    left == right
  end

  defp trim(l_range, r_range) do
    ll = Enum.count(l_range)
    lr = Enum.count(r_range)

    if ll > lr do
      {Enum.take(l_range, lr), r_range}
    else
      {l_range, Enum.take(r_range, ll)}
    end
  end

  defp scan_row(y, max_x, map) do
    for x <- 1..max_x do
      Map.get(map, {x, y})
    end
  end

  defp scan_column(x, max_y, map) do
    for y <- 1..max_y do
      Map.get(map, {x, y})
    end
  end

  defp reflection_value({cols, rows}) do
    Enum.sum(cols) + (rows |> Enum.map(& &1 * 100) |> Enum.sum())
  end

  defp smudge_and_reflect(map) do
    og = map |> find_reflection()

    map
    |> smudge()
    |> Enum.reduce_while(og, fn map, {cols0, rows0} = acc ->
      {cols, rows} = find_reflection(map)
      case {cols -- cols0, rows -- rows0} do
        {[], []} -> {:cont, acc}
        new      -> {:halt, new}
      end
    end)
  end

  defp smudge(map) do
    {max_x, max_y} = map |> Map.keys() |> Enum.max()

    for x <- 1..max_x, y <- 1..max_y do
      Map.update(map, {x, y}, nil, fn "#" -> "."; "." -> "#" end)
    end
  end
end

input = File.read!("priv/13.txt") |> Aoc.Thirteen.parse()

input |> Aoc.Thirteen.part_one() |> IO.inspect(label: "part 1")
input |> Aoc.Thirteen.part_two() |> IO.inspect(label: "part 2")

2 Likes

I had fun with this one. I’m pretty happy with my approach to finding the symmetries.

3 Likes