Jump to content

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

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('day6', function(myrequire)
--[[ DAY 6 ]]--
local lpegrex = myrequire('llpeg.lpegrex')

--[[ PARSING ]]--
local patt = lpegrex.compile([[
Start <== `Time:` {:times: NumberList :} nl
          `Distance:` {:distances: NumberList :} nl* !.
NumberList <-- {| (Number SKIP)* |}
Number <-- %d+ -> tonumber
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))
   -- post process
   local games = {}
   for i,time in ipairs(ast.times) do
      local dist = ast.distances[i]
      table.insert(games, { time = time, dist = dist })
   end
   return games
end

function one_game(race)
   local t,d = race.time,race.dist
   -- solve quadratic formula
   local discrim = (t*t) - 4*d
   if d < 0 then return 0 end -- no way to win the race
   discrim = math.sqrt(discrim)
   local min = math.ceil((t - discrim) / 2)
   local max = math.floor((t + discrim) / 2)
   -- tricky corner case: we can't tie the record we must win it
   if ((race.time - min) * min) <= race.dist then
      min = min + 1 -- bump it up
   end
   if ((race.time - max) * max) <= race.dist then
      max = max - 1 -- bump it down
   end
   -- print("min", min, "max", max)
   assert(min > 0 and max < race.time)
   if min <= max then return (max - min) + 1 end
   return 0
end

function all_ways(source)
   local races = parse(source)
   local prod = 1
   for i,r in ipairs(races) do
      local ways = one_game(r)
      --print("Race", i, "Time", r.time, "Dist", r.dist, "Ways", ways)
      prod = prod * ways
   end
   return prod
end

function part1(source)
   return all_ways(source)
end

function part2(source)
   -- remove all spaces!
   source = string.gsub(source, ' ', '')
   source = string.gsub(source, ':', ': ')
   return all_ways(source)
end

--[[ CLI start ] ]--
local source = io.input("day6.input"):read("a")
print(part1(source))
print(part2(source))
--[ [ CLI end ]]--

return {
   part1 = function(frame)
      local s = mw.title.new(frame.args[1]):getContent()
      return part1(s)
   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('day6')
end)()