Jump to content

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

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

register('day2', function(myrequire)
--[[ DAY 2 ]]--

local lpegrex = myrequire('llpeg.lpegrex')

--[[ PARSING ]]--

local patt = lpegrex.compile([[
Lines <== nl* Game (nl+ Game)* nl*
Game <== `Game` {:id: Number :} `:` {:turns: Turns :}
Turns <== Turn (`;` Turn)*
Turn <== Color (`,` Color)*
Color <== {:count: Number :} SKIP {:color: ColorName :}
Number <-- %d+ -> tonumber
ColorName <-- `red` / `green` / `blue`
nl <- %nl
SKIP <- [ ]*
NAME_SUFFIX <- [_%w]+
]])

function parse(source)
   --print(inspect(source))
   local ast, errlabel, pos = patt:match(source)
   if not ast then
      local lineno, colno, line = lpegrex.calcline(source, pos)
      local colhelp = string.rep(' ', colno-1)..'^'
      error('syntax error: '..lineno..':'..colno..': '..errlabel..
            '\n'..line..'\n'..colhelp)
   end
   --print('Parsed with success!')
   --print(inspect(ast))
   return ast
end

--[[ PART 1 ]]--

function find(limit, color)
   for j = 1, #limit do
      local draw = limit[j]
      if draw.color == color then
         return draw
      end
   end
   return nil
end

function possible(game, limit)
   for i = 1, #game.turns do
      local turn = game.turns[i]
      for j = 1, #turn do
         local draw = turn[j]
         local match = find(limit, draw.color)
         if match == nil then
            return false -- impossible, no matching color
         elseif match.count < draw.count then
            return false -- impossible, not enough of this color
         end
      end
   end
   return true -- this game is possible
end

function sum_possible(source, limit)
   local games = parse(source)
   -- print(#games, "games\n")

   local limit = parse(limit)[1].turns[1]
   --print(inspect(limit))
   local sum = 0
   for i = 1, #games do
      local game = games[i]
      if possible(game, limit) then
         -- print("Game " .. game.id .. " is possible!")
         sum = sum + game.id
      end
   end
   -- print("Sum", sum)
   return sum
end

--[[ PART 2 ]]--

function min_cubes(game)
   local red, green, blue = 0,0,0
   for i = 1, #game.turns do
      local turn = game.turns[i]
      for j = 1, #turn do
         local draw = turn[j]
         if draw.color == 'red' then
            red = math.max(red, draw.count)
         elseif draw.color == 'green' then
            green = math.max(green, draw.count)
         elseif draw.color == 'blue' then
            blue = math.max(blue, draw.count)
         else
            error("what color is this?")
         end
      end
   end
   return red, green, blue
end

function power(game)
   local red, green, blue = min_cubes(game)
   return red * green * blue
end

function sum_powers(source)
   local games = parse(source)
   -- print(#games, "games\n")

   local sum = 0
   for i = 1, #games do
      local game = games[i]
      sum = sum + power(game)
   end
   -- print("Sum", sum)
   return sum
end

function part1(source, limit)
   return sum_possible(source, limit)
end

function part2(source)
   return sum_powers(source)
end

--[[ CLI start ] ]--
-- local source = "Game 1: 1 blue\nGame 2: 2 red"
--local source = io.input("day2.example"):read("a")
--local limit = "Game 0: 12 red, 13 green, 14 blue"

local source = io.input("day2.input"):read("*a")
local limit = "Game 0: 12 red, 13 green, 14 blue"

print("Sum", sum_possible(source, limit))
print("Power", sum_powers(source))
--[ [ CLI end ]]--

return {
   part1 = function(frame)
      local s = mw.title.new(frame.args[1]):getContent()
      local limit = frame.args[2]
      return part1(s, limit)
   end,
   part2 = function(frame)
      local s = mw.title.new(frame.args[1]):getContent()
      return part2(s)
   end,
}

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('day2')
end)()