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

Advent of Code 2023

2023/day13.livemd

Advent of Code 2023

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

Day 13

input =
  "https://adventofcode.com/2023/day/13/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample = """
#.##..##.
..#.##.#.
##......#
##......#
..#.##.#.
..##..##.
#.#.##.#.

#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#
"""
defmodule A do
  def parse(input) do
    input
    |> String.split("\n\n", trim: true)
    |> Enum.map(fn group ->
      String.split(group, "\n", trim: true)
      |> Enum.map(fn line -> String.split(line, "", trim: true) end)
    end)
  end

  def part1(input) do
    input
    |> parse()
    |> Enum.map(&pattern_value/1)
    |> Enum.map(fn
      {:horizontal, i} -> i * 100
      {:vertical, i} -> i
    end)
    |> Enum.sum()
  end

  def pattern_value(pattern, skip_index \\ nil) do
    [:horizontal, :vertical]
    |> Stream.map(fn dir ->
      case skip_index do
        {^dir, i} ->
          {dir, find_split(pattern, dir, i)}

        _ ->
          {dir, find_split(pattern, dir, nil)}
      end
    end)
    |> Enum.find(&(&1 |> elem(1) != nil))
  end

  def find_split(pattern, :vertical, skip_index) do
    pattern =
      pattern
      |> List.zip()
      |> Enum.map(&Tuple.to_list/1)

    find_split(pattern, :horizontal, skip_index)
  end

  def find_split(pattern, :horizontal, skip_index) do
    pattern
    |> Stream.chunk_every(2, 1, :discard)
    |> Stream.with_index(1)
    |> Stream.filter(fn {[a, b], _i} -> a == b end)
    |> Enum.find(fn {_, i} ->
      if i == skip_index do
        false
      else
        {a, b} = pattern |> Enum.split(i)

        a_len = length(a)
        b_len = length(b)

        cond do
          a_len > b_len ->
            Enum.reverse(a) |> Enum.split(b_len) |> elem(0) == b

          a_len < b_len ->
            a == Enum.split(b, a_len) |> elem(0) |> Enum.reverse()

          true ->
            a == Enum.reverse(b)
        end
      end
    end)
    |> case do
      {_, i} -> i
      nil -> nil
    end
  end

  def part2(input) do
    input
    |> parse()
    |> Enum.map(fn pattern ->
      rows = length(pattern)
      cols = length(hd(pattern))

      {dir, i} = pattern_value(pattern)

      for(r <- 0..rows, c <- 0..cols, do: {r, c})
      # smudge
      |> Stream.map(fn {r, c} -> smudge(pattern, r, c) end)
      |> Stream.map(fn pattern ->
        pattern_value(pattern, {dir, i})
      end)
      |> Enum.find(&amp;(&amp;1 != nil))
    end)
    |> Enum.map(fn
      {:horizontal, i} -> i * 100
      {:vertical, i} -> i
    end)
    |> Enum.sum()
  end

  def smudge(pattern, r, c) do
    List.update_at(pattern, r, fn row ->
      List.update_at(row, c, fn
        "#" -> "."
        "." -> "#"
      end)
    end)
  end
end

Part 1

sample
|> A.part1()
input
|> A.part1()

Part 2

sample
|> A.part2()
input
|> A.part2()