Advent of Code 2023 - Day 3

My beginner’s solution, Day 3 part 1.
My focus was on readability. How I did?

defmodule Day03 do

  def part1(input) do
    # " 467..
    #   ...*.
    #   ..35  "    

    schematic = input
    |> String.split("\n")
    |> Enum.map(&String.graphemes/1)
# [ ["4", "6", "7", ".", "."],
#   [".", ".", ".", "*", "."],
#   [".", ".", "3", "5", "."]  ]
    
    matrix_of_positions_adjecent_to_symbols(schematic)
# [ [false, false, true, true, true],
#   [false, false, true, false, true],
#   [false, false, true, true, true]  ]
    |> Enum.zip(schematic)
    |> Enum.map(fn {m, s} -> Enum.zip(s, m) end)
# [ [{"4", false}, {"6", false}, {"7", true}, {".", true}, {".", true}],
#   [{".", false}, {".", false}, {".", true}, {"*", false}, {".", true}],
#   [{".", false}, {".", false}, {".", true}, {"3", true}, {"5", true}]  ]
    |> Enum.map(&extract_valid_numbers_from_zipped_row/1)
    |> List.flatten
    |> Enum.sum   
  end

  def matrix_of_positions_adjecent_to_symbols(schematic) do
    blank_matrix = List.duplicate(false, 140)
    |> List.duplicate(140)
# [ [false, false, false, false, false],
#   [false, false, false, false, false],
#   [false, false, false, false, false]  ]    

    schematic
    |> Enum.map(&Enum.with_index/1) 
    |> Enum.with_index
# [ {[{"4", 0}, {"6", 1}, {"7", 2}, {".", 3}, {".", 4}], 0},
#   {[{".", 0}, {".", 1}, {".", 2}, {"*", 3}, {".", 4}], 1},
#   {[{".", 0}, {".", 1}, {".", 2}, {"3", 3}, {"5", 4}], 2}  ]
    |> Enum.reduce(blank_matrix, 
         fn {row, row_index}, acc -> 
            Enum.reduce(row, acc, 
              fn {p, index}, acc -> 
                cond do
                  is_symbol?(p) -> encrircle_with_true_at(acc, row_index, index)
                  true -> acc
                end  
             end)
        end)
# [ [false, false, true, true, true],
#   [false, false, true, false, true],
#   [false, false, true, true, true]  ]
  end

  def is_symbol?(grapheme) do
    ["*", "&", "@", "/", "+", "-", "%", "$", "=", "#"]
    |> Enum.member?(grapheme)
  end

  def is_digit?(grapheme) do
    ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    |> Enum.member?(grapheme)
  end
  
  def encrircle_with_true_at(matrix, row, pos) do
    matrix
    |> replace_with_true_at(row-1, pos-1)
    |> replace_with_true_at(row-1, pos)
    |> replace_with_true_at(row-1, pos+1)
    |> replace_with_true_at(row, pos-1)
    |> replace_with_true_at(row, pos+1)
    |> replace_with_true_at(row+1, pos-1)
    |> replace_with_true_at(row+1, pos)
    |> replace_with_true_at(row+1, pos+1)
  end
      
  def replace_with_true_at(matrix, row, position) do
    matrix
    |> List.pop_at(row)
    |> elem(0)
    |> List.replace_at(position, true)
    |> then(&List.replace_at(matrix, row, &1))
  end

  def extract_valid_numbers_from_zipped_row(row) do  
# [{"4", false}, {"6", false}, {"7", true}, {".", true}, {".", true}]
    row
    |> Enum.chunk_by(fn {n, _} -> is_digit?(n) end)
    |> Enum.filter(fn x -> x |> List.first |> elem(0) |> is_digit? end)
    |> Enum.map(&Enum.unzip/1)
    # [{["4", "6", "7"], [false, false, true]}, {[...],[...]}, ...]
    |> Enum.filter(fn {_, truth_list} -> Enum.any?(truth_list) end)
    |> Enum.map(fn x -> x
                  |> elem(0)
                  |> Enum.join
                  |> String.to_integer
                end)
  end

end