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

Advent of Code 2023

2023/day03.livemd

Advent of Code 2023

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

Day 3

input =
  "https://adventofcode.com/2023/day/3/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample = """
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
"""
defmodule A do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.with_index()
    |> Enum.map(fn {line, i} ->
      nums = Regex.scan(~r/(\d+)/, line, return: :index) |> Enum.map(&hd/1)

      num_strings =
        Regex.scan(~r/(\d+)/, line, capture: :all_but_first)
        |> Enum.map(&hd/1)
        |> Enum.map(&String.to_integer/1)

      symbols = Regex.scan(~r/([^\.\d])/, line, return: :index) |> Enum.map(&hd/1)
      symbol_strings = Regex.scan(~r/([^\.\d])/, line, capture: :all_but_first) |> Enum.map(&hd/1)

      {i,
       %{
         numbers: Enum.zip(num_strings, nums),
         symbols: Enum.zip(symbol_strings, symbols)
       }}
    end)
    |> Enum.into(%{})
  end

  def sum_valid(data) do
    # data |> IO.inspect()

    Stream.iterate(0, &(&1 + 1))
    |> Enum.reduce_while(0, fn i, acc ->
      if Map.has_key?(data, i) do
        prev_row = Map.get(data, i - 1, %{numbers: [], symbols: []})
        current_row = Map.fetch!(data, i)
        next_row = Map.get(data, i + 1, %{numbers: [], symbols: []})

        symbols =
          Enum.concat([prev_row.symbols, current_row.symbols, next_row.symbols])
          |> Enum.map(fn {_symbol, {start_index, 1}} -> start_index end)

        # IO.inspect([current_row.numbers, symbols])

        numbers =
          current_row.numbers
          |> Enum.filter(fn {_n, {start_index, len}} ->
            symbols
            |> Enum.any?(fn symbol_i ->
              # IO.inspect({symbol_i, start_index - 1, start_index + len})
              symbol_i >= start_index - 1 &amp;&amp; symbol_i <= start_index + len
            end)
          end)
          |> Enum.map(fn {n, _} -> n end)

        {:cont, Enum.sum(numbers) + acc}
      else
        {:halt, acc}
      end
    end)
  end

  def sum_gear_ratios(data) do
    Stream.iterate(0, &amp;(&amp;1 + 1))
    |> Enum.reduce_while(0, fn i, acc ->
      if Map.has_key?(data, i) do
        prev_row = Map.get(data, i - 1, %{numbers: [], symbols: []})
        current_row = Map.fetch!(data, i)
        next_row = Map.get(data, i + 1, %{numbers: [], symbols: []})

        gear_ratios =
          current_row.symbols
          |> Enum.filter(fn {symbol, _} -> symbol == "*" end)
          |> Enum.map(fn {_, {symbol_i, 1}} ->
            adjacent_numbers =
              Enum.concat([prev_row.numbers, current_row.numbers, next_row.numbers])
              |> Enum.filter(fn {_n, {start_index, len}} ->
                symbol_i >= start_index - 1 &amp;&amp; symbol_i <= start_index + len
              end)

            if Enum.count(adjacent_numbers) > 1 do
              adjacent_numbers
              |> Enum.map(fn {n, _} -> n end)
              |> Enum.product()
            else
              0
            end
          end)

        {:cont, Enum.sum(gear_ratios) + acc}
      else
        {:halt, acc}
      end
    end)
  end

  def part1(input) do
    input
    |> parse()
    |> sum_valid()
  end

  def part2(input) do
    input
    |> parse()
    |> sum_gear_ratios()
  end
end

Part 1

# sample
input
|> A.part1()

Part 2

# sample
input
|> A.part2()