Powered by AppSignal & Oban Pro

Day 3: Gear Ratios – Advent of Code

2023/03.livemd

Day 3: Gear Ratios – Advent of Code

input =
  File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/03.txt")
  |> Stream.map(&String.trim/1)

test_input = "467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598.." |> String.split("\n")

Part One

matrix =
  input
  |> Stream.map(&String.split(&1, "", trim: true))

matrix |> Enum.to_list()
lookup_map =
  matrix
  |> Stream.map(&Enum.with_index(&1))
  |> Enum.with_index()
  |> Enum.reduce(Map.new(), fn {row, y}, acc ->
    Map.merge(
      acc,
      Enum.reduce(row, Map.new(), fn {val, x}, macc ->
        Map.put(macc, {x, y}, val)
      end)
    )
  end)
line_neighbors = fn mat, x, y ->
  (x - 1)..(x + 1)
  |> Enum.map(&Map.get(mat, {&1, y}))
  |> Enum.reject(&is_nil/1)
end

line_neighbors.(lookup_map, 0, 0)
neighbors = fn mat, x, y ->
  (y - 1)..(y + 1)
  |> Enum.map(&line_neighbors.(mat, x, &1))
  |> Enum.reject(&Enum.empty?/1)
  |> Enum.flat_map(fn x -> x end)
end

neighbors.(lookup_map, 3, 0)
has_char = fn arr ->
  arr
  |> Enum.filter(fn n -> n != "." && Integer.parse(n) == :error end)
  |> then(&(!Enum.empty?(&1)))
end

has_char.(neighbors.(lookup_map, 3, 0))
maxX = matrix |> Enum.count() |> then(&(&1 - 1))
maxY = matrix |> Enum.at(0) |> Enum.count() |> then(&(&1 - 1))

{maxX, maxY}
handle_c = fn x, y ->
  value =
    lookup_map
    |> Map.get({x, y})

  if Integer.parse(value) == :error do
    {nil, x, y, false}
  else
    {value, x, y, has_char.(neighbors.(lookup_map, x, y))}
  end
end

combine = fn {value, x, _y, state}, acc ->
  %{a: a, v: v, n: n} = acc
  is_last = x == maxX

  new_n = if value != nil, do: n <> value, else: n
  new_v = state || v
  new_a = if v == true, do: [new_n | a], else: a

  cond do
    (value == nil && n != "") || (value != nil && is_last) -> %{a: new_a, v: false, n: ""}
    value != nil -> acc |> Map.put(:n, new_n) |> Map.put(:v, new_v)
    true -> acc
  end
end

handle_row = fn row ->
  row
  |> Enum.reduce(%{a: [], v: false, n: ""}, &combine.(&1, &2))
  |> Map.get(:a)
  |> Enum.map(fn n -> String.to_integer(n) end)
end

Solution for part one:

0..maxY
|> Enum.map(fn y -> 0..maxX |> Enum.map(&handle_c.(&1, y)) end)
|> Enum.map(&handle_row.(&1))
|> Enum.flat_map(fn n -> n end)
|> Enum.sum()

# |> then(&(&1 == 4361))

Part Two

find_whole_number = fn x, y ->
  curr = Map.get(lookup_map, {x, y})

  if Integer.parse(curr) == :error do
    false
  else
    reduce_num = fn x, acc, action ->
      element = Map.get(lookup_map, {x, y})

      if Integer.parse(element) == :error do
        {:halt, acc}
      else
        result = if action == :append, do: acc <> element, else: element <> acc
        {:cont, result}
      end
    end

    prefix =
      if x > 0, do: (x - 1)..0 |> Enum.reduce_while("", &reduce_num.(&1, &2, :prepend)), else: ""

    suffix =
      if x < maxX,
        do: (x + 1)..maxX |> Enum.reduce_while("", &reduce_num.(&1, &2, :append)),
        else: ""

    (prefix <> curr <> suffix)
    |> String.to_integer()
  end
end

line_neighbor_numbers = fn x, y ->
  curr = Map.get(lookup_map, {x, y})

  if Integer.parse(curr) == :error do
    [find_whole_number.(x - 1, y), find_whole_number.(x + 1, y)]
  else
    [find_whole_number.(x, y)]
  end
end

line_neighbor_numbers.(12, 1)
neighbor_numbers = fn x, y ->
  (y - 1)..(y + 1)
  |> Enum.map(&line_neighbor_numbers.(x, &1))
  |> Enum.reject(&Enum.empty?/1)
  |> Enum.flat_map(fn x -> x end)
  |> Enum.reject(&(!&1))
end

neighbor_numbers.(11, 2)
find_stars = fn x, y ->
  if Map.get(lookup_map, {x, y}) == "*" do
    neighbor_numbers.(x, y)
  else
    false
  end
end

Solution for part two:

0..maxY
|> Enum.map(fn y -> 0..maxX |> Enum.map(&find_stars.(&1, y)) end)
|> Enum.flat_map(fn n -> n end)
|> Enum.reject(&(!&1))
|> Enum.filter(&(Enum.count(&1) == 2))
|> Enum.map(fn [a, b] -> a * b end)
|> Enum.sum()

# |> Kernel.==(467835)