Jump to content

Module:User:Cscott/Advent Of Code 2023/Day 3

From Wikipedia, the free encyclopedia
return (function()
local builders = {}
local function register(name, f)
  builders[name] = f
end
register('advent.compat', function() return require [[Module:User:Cscott/compat]] end)

register('day3', function(myrequire)
--[[
   Day 1, first part; advent of code 2023
]]--

local compat = myrequire('advent.compat')
--local inspect = require 'inspect'

--[[
   Infrastructure
]]--

function split(str)
   lines = {}
   for s in string.gmatch(str, "[^\r\n]+") do
      table.insert(lines, s)
   end
   return lines
end

function part1(frame)
   local s = frame:expandTemplate{ title = frame.args[1] }
   return day1a(split(s))
end

function part2(frame)
   local s = frame:expandTemplate{ title = frame.args[1] }
   return day1b(split(s))
end

--[[
   Part 1
]]--

function make_graph(lines)
   local graph = {}
   for row = 1,#lines do
      local l = lines[row]
      graph[row] = {}
      for col = 1,#l do
         graph[row][col] = l:sub(col, col)
      end
   end
   return graph, #graph, #(graph[1])
end

function is_symbol(c)
   return c:match("[0-9.]") == nil
end
function is_digit(c)
   return not (c:match("[0-9]") == nil)
end
function char_at(graph, row, col)
   if row < 1 then
      return "."
   elseif row > #graph then
      return "."
   end
   r = graph[row]
   if col < 1 then
      return "."
   elseif col > #r then
      return "."
   end
   return r[col]
end

function symbol_around(graph, row, col)
   for r = row-1, row+1 do
      for c = col-1, col+1 do
         if is_symbol(char_at(graph, r, c)) then
            return true
         end
      end
   end
   return false
end

function day1a(lines)
   local graph, rows, cols = make_graph(lines)
   -- look for numbers
   local in_number = false
   local seen_symbol = false
   local seen_number = ""
   local sum = 0
   for row = 1, rows do
      for col = 1, cols do
         local c = graph[row][col]
         if in_number then
            if is_digit(c) then
               -- continue our number
               seen_number = seen_number .. c
               seen_symbol = seen_symbol or symbol_around(graph, row, col)
            else
               -- finish our number
               --print("Found", seen_number, seen_symbol)
               in_number = false
               if seen_symbol then
                  local n = 0 + seen_number -- coerce to int
                  sum = sum + n
               end
            end
         else -- not in_number, see if we want to start one
            if is_digit(c) then
               in_number = true
               seen_number = c
               seen_symbol = symbol_around(graph, row, col)
            end
         end
      end
   end
   return sum
end

--[[
Part 2!
]]--

function gears_around(graph, row, col)
   gears = {}
   for r = row-1, row+1 do
      for c = col-1, col+1 do
         if char_at(graph, r, c) == "*" then
            if gears[r] == nil then
               gears[r] = {}
            end
            gears[r][c] = true
         end
      end
   end
   return gears
end

function day1b(lines)
   local graph, rows, cols = make_graph(lines)
   -- look for numbers
   local in_number = false
   local seen_gears = {}
   local seen_number = ""
   local number_for_gear = {}

   for row = 1, rows do
      for col = 1, cols do
         local c = graph[row][col]
         if in_number then
            if is_digit(c) then
               -- continue our number
               seen_number = seen_number .. c
               for r,row in pairs(gears_around(graph, row, col)) do
                  for c,_ in pairs(row) do
                     if seen_gears[r] == nil then
                        seen_gears[r] = {}
                     end
                     seen_gears[r][c] = true
                  end
               end
            else
               -- finish our number
               -- print("Found", seen_number, inspect(seen_gears))
               in_number = false
               local n = 0 + seen_number -- coerce to int
               for r,row in pairs(seen_gears) do
                  for c,_ in pairs(row) do
                     if number_for_gear[r] == nil then
                        number_for_gear[r] = {}
                     end
                     if number_for_gear[r][c] == nil then
                        number_for_gear[r][c] = {}
                     end
                     table.insert(number_for_gear[r][c], n)
                  end
               end
            end
         else -- not in_number, see if we want to start one
            if is_digit(c) then
               in_number = true
               seen_number = c
               seen_gears = gears_around(graph, row, col)
            end
         end
      end
   end
   -- okay, now find gears bordering exactly two numbers
   local sum = 0
   for r,row in pairs(number_for_gear) do
      for c,nums in pairs(row) do
         if #nums > 2 then
            -- print("Found",#nums,":",inspect(nums),"???")
         elseif #nums == 2 then
            sum = sum + (nums[1] * nums[2])
         end
      end
   end
   return sum
end


--[[
   Testing
]]--

--print(inspect(day1a(split(io.input("day3.example"):read("a")))))
--print(inspect(day1a(split(io.input("day3.input"):read("a")))))

--print(inspect(day1b(split(io.input("day3.example"):read("a")))))
--print(inspect(day1b(split(io.input("day3.input"):read("a")))))


return {
   part1 = part1,
   part2 = part2,
}

end)

local modules = {}
modules['table'] = require('table')
modules['string'] = require('string')
modules['strict'] = {}
local function myrequire(name)
  if modules[name] == nil then
    modules[name] = true
    modules[name] = (builders[name])(myrequire)
  end
  return modules[name]
end
return myrequire('day3')
end)()