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

return (function()

local builders = {}

local function register(name, f)

builders[name] = f

end

register('llpeg', function() return require Module:User:Cscott/llpeg end)

register('pqueue', function(myrequire)

--[[ Priority Queue implemented in lua, based on a binary heap.

Copyright (C) 2017 Lucas de Morais Siqueira

License: zlib

This software is provided 'as-is', without any express or implied

warranty. In no event will the authors be held liable for any damages

arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,

including commercial applications, and to alter it and redistribute it

freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not

claim that you wrote the original software. If you use this software

in a product, an acknowledgement in the product documentation would be

appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must not be

misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.

]]--

-- modified by xxopxe@gmail.com

local floor = math.floor

local PriorityQueue = {}

PriorityQueue.__index = PriorityQueue

setmetatable(

PriorityQueue,

{

__call = function ()

local new = {}

setmetatable(new, PriorityQueue)

new:initialize()

return new

end

}

)

function PriorityQueue:initialize()

--[[ Initialization.

Example:

PriorityQueue = require "priority_queue"

pq = PriorityQueue()

]]--

self.heap_val = {}

self.heap_pri = {}

self.current_size = 0

end

function PriorityQueue:empty()

return self.current_size == 0

end

function PriorityQueue:size()

return self.current_size

end

function PriorityQueue:swim()

-- Swim up on the tree and fix the order heap property.

local heap_val = self.heap_val

local heap_pri = self.heap_pri

local floor = floor

local i = self.current_size

while floor(i / 2) > 0 do

local half = floor(i / 2)

if heap_pri[i] < heap_pri[half] then

heap_val[i], heap_val[half] = heap_val[half], heap_val[i]

heap_pri[i], heap_pri[half] = heap_pri[half], heap_pri[i]

end

i = half

end

end

function PriorityQueue:put(v, p)

--[[ Put an item on the queue.

Args:

v: the item to be stored

p(number): the priority of the item

]]--

--

self.current_size = self.current_size + 1

self.heap_val[self.current_size] = v

self.heap_pri[self.current_size] = p

self:swim()

end

function PriorityQueue:sink()

-- Sink down on the tree and fix the order heap property.

local size = self.current_size

local heap_val = self.heap_val

local heap_pri = self.heap_pri

local i = 1

while (i * 2) <= size do

local mc = self:min_child(i)

if heap_pri[i] > heap_pri[mc] then

heap_val[i], heap_val[mc] = heap_val[mc], heap_val[i]

heap_pri[i], heap_pri[mc] = heap_pri[mc], heap_pri[i]

end

i = mc

end

end

function PriorityQueue:min_child(i)

if (i * 2) + 1 > self.current_size then

return i * 2

else

if self.heap_pri[i * 2] < self.heap_pri[i * 2 + 1] then

return i * 2

else

return i * 2 + 1

end

end

end

function PriorityQueue:pop()

-- Remove and return the top priority item

local heap_val = self.heap_val

local heap_pri = self.heap_pri

local retval, retprio = heap_val[1], heap_pri[1]

heap_val[1], heap_pri[1] = heap_val[self.current_size], heap_pri[self.current_size]

heap_val[self.current_size], heap_pri[self.current_size] = nil, nil

self.current_size = self.current_size - 1

self:sink()

return retval, retprio

end

function PriorityQueue:peek()

-- return the top priority item

return self.heap_val[1], self.heap_pri[1]

end

return PriorityQueue

end)

register('advent.compat', function() return require Module:User:Cscott/compat end)

register('eq', function(myrequire)

-- "fix" lua's eq metamethod to be consistent with __add etc, that is:

-- try the metamethod if any operand is not a number

local function eq(a, b)

local tya, tyb = type(a), type(b)

if tya ~= 'number' or tyb ~= 'number' then

local op = nil

local mt = getmetatable(a)

if mt ~= nil then op = mt.__eq end

if op == nil then

mt = getmetatable(b)

if mt ~= nil then op = mt.__eq end

end

if op ~= nil then

return op(a, b)

end

end

return a == b

end

return eq

end)

register('lt', function(myrequire)

-- "fix" lua's lt metamethod to be consistent with __add etc, that is:

-- try the metamethod if any operand is not a number

local function lt(a, b)

local tya, tyb = type(a), type(b)

if tya ~= 'number' or tyb ~= 'number' then

local op = nil

local mt = getmetatable(a)

if mt ~= nil then op = mt.__lt end

if op == nil then

mt = getmetatable(b)

if mt ~= nil then op = mt.__lt end

end

if op ~= nil then

return op(a, b)

end

end

return a < b

end

return lt

end)

register('bignum', function(myrequire)

local compat = myrequire('advent.compat')

local eq = myrequire('eq')

local lt = myrequire('lt')

-- poor man's bignum library

local RADIX = 1000 -- power of 10 to make printout easier

local function maxdigits(a, b)

if #a > #b then return #a end

return #b

end

local function ltz(a)

if type(a) == 'number' then

return a < 0

end

return a.negative or false

end

local BigNum = {}

BigNum.__index = BigNum

function BigNum:new(n)

if n == nil then n = 0 end

assert(type(n)=='number')

if n < 0 then

return setmetatable( {-n, negative=true}, self):normalize()

else

return setmetatable( {n}, self):normalize()

end

end

function BigNum:__tostring()

local result = {}

if self.negative then

table.insert(result, "-")

end

local first = true

for i=#self,1,-1 do

local n = self[i]

if n ~= 0 or not first then

local s = tostring(n)

if first then

first = false

else

while #s < 3 do s = '0' .. s end

end

table.insert(result, s)

end

end

if #result == 0 then return "0" end

return table.concat(result)

end

function BigNum:toNumber()

local m = 1

local sum = 0

for i=1,#self do

sum = sum + (self[i] * m)

m = m * RADIX

end

return sum

end

function BigNum:normalize()

local i = 1

local sawNonZero = false

while self[i] ~= nil do

assert(self[i] >= 0)

if self[i] > 0 then

sawNonZero = true

end

if self[i] >= 1000 then

local carry = math.floor(self[i] / 1000)

self[i] = self[i] % 1000

self[i+1] = (self[i+1] or 0) + carry

end

i = i + 1

end

if not sawNonZero then

self.negative = nil -- -0 is 0

end

return self

end

function BigNum:copy()

local r = BigNum:new()

for i=1,#self do

r[i] = self[i]

end

r.negative = self.negative

return r

end

function BigNum.__unm(a)

if eq(a, 0) then return a end -- -0 is 0

local r = a:copy()

r.negative = not r.negative

return r

end

function BigNum.__add(a, b)

if ltz(b) then

if ltz(a) then

return -((-a) + (-b))

end

return a - (-b)

end

if ltz(a) then

return b - (-a)

end

assert(not ltz(a))

assert(not ltz(b))

if type(a) == 'number' then

a,b = b,a

end

assert(not a.negative)

local r = a:copy()

if type(b) == 'number' then

assert(b >= 0)

r[1] = (r[1] or 0) + b

else

assert(not b.negative)

for i=1,#b do

r[i] = (r[i] or 0) + b[i]

end

end

return r:normalize()

end

function BigNum.__lt(a, b)

if ltz(a) then

if ltz(b) then

return (-a) > (-b)

end

return true

elseif ltz(b) then

return false

end

if type(a) == 'number' then a = BigNum:new(a) end

if type(b) == 'number' then b = BigNum:new(b) end

for i=maxdigits(a,b),1,-1 do

if (a[i] or 0) < (b[i] or 0) then return true end

if (a[i] or 0) > (b[i] or 0) then return false end

end

return false -- they are equal

end

function BigNum.__le(a, b)

return not (a > b)

end

function BigNum.__eq(a, b)

if ltz(a) ~= ltz(b) then return false end

if type(a) == 'number' then a = BigNum:new(a) end

if type(b) == 'number' then b = BigNum:new(b) end

for i=1,maxdigits(a,b) do

if (a[i] or 0) ~= (b[i] or 0) then return false end

end

return true

end

function BigNum.__sub(a, b)

if ltz(b) then

return a + (-b)

end

if ltz(a) then

return -((-a) + b)

end

if type(a) == 'number' then a = BigNum:new(a) end

if type(b) == 'number' then b = BigNum:new(b) end

if b > a then

return -(b - a)

end

local r = a:copy()

local borrow = 0

for i=1,maxdigits(a,b) do

r[i] = (r[i] or 0) - (b[i] or 0) - borrow

borrow = 0

while r[i] < 0 do

r[i] = r[i] + RADIX

borrow = borrow + 1

end

assert(r[i] >= 0)

end

assert(borrow == 0)

return r:normalize() -- shouldn't actually be necessary?

end

function BigNum.__mul(a, b)

if type(a) == 'number' then

a,b = b,a

end

local r = BigNum:new()

if type(b) == 'number' then

if b < 0 then r.negative = true ; b = -b ; end

assert(b>=0)

for i=1,#a do

r[i] = a[i] * b

end

if a.negative then r.negative = not r.negative end

return r:normalize()

end

for i=1,#a do

for j=1,#b do

assert(a[i] >= 0)

assert(b[j] >= 0)

local prod = a[i] * b[j]

r[i+j-1] = (r[i+j-1] or 0) + prod

end

end

r.negative = a.negative

if b.negative then r.negative = not r.negative end

return r:normalize()

end

function toBinary(a)

assert(not a.negative)

local bits = {}

local RADIX_DIV_2 = compat.idiv(RADIX, 2)

while a[1] ~= nil do

table.insert(bits, a[1] % 2)

for i=1,#a do

a[i] = compat.idiv(a[i], 2) + ((a[i+1] or 0) % 2) * RADIX_DIV_2

end

if a[#a] == 0 then a[#a] = nil end

end

return bits

end

local function divmod(a, b)

if eq(b, 0) then error("division by zero") end

if ltz(b) then

if ltz(a) then return divmod(-a, -b) end

local q,r = divmod(a, -b)

if lt(0, r) then q = q + 1 end

return -q, -r

elseif ltz(a) then

local q,r = divmod(-a, b)

if lt(0, r) then q = q + 1 end

return -q, r

end

-- ok, a and b are both positive now

assert(not ltz(a))

assert(not ltz(b))

if type(a) == 'number' then a = BigNum:new(a) end

if type(b) == 'number' then b = BigNum:new(b) end

local N,D = a,b

local Q,R = BigNum:new(0), BigNum:new(0)

local Nbits = toBinary(N:copy())

for i=#Nbits,1,-1 do

--print("R=",R,"Q=",Q)

for i=1,#R do R[i] = R[i] * 2 end

if Nbits[i] > 0 then R[1] = R[1] + 1 end

R:normalize()

for i=1,#Q do Q[i] = Q[i] * 2 end

if R >= D then

R = R - D

Q[1] = Q[1] + 1

end

Q:normalize()

end

return Q,R

end

function BigNum.__idiv(a, b)

local q,r = divmod(a, b)

return q

end

function BigNum.__mod(a, b)

local q,r = divmod(a, b)

return r

end

--[[

print(BigNum:new(4) >= BigNum:new(2))

print(BigNum:new(4) > BigNum:new(2))

print(BigNum:new(2) >= BigNum:new(2))

print(BigNum:new(2) > BigNum:new(2))

print(BigNum:new(4653) // BigNum:new(210))

]]--

return BigNum

end)

register('util', function(myrequire)

local function read_wiki_input(func)

return function (frame, ...)

if type(frame)=='string' then

frame = { args = { frame, ... } }

end

local title = mw.title.new(frame.args[1])

local source = title:getContent()

if source == nil then

error("Can't find title " .. tostring(title))

end

source = source:gsub("^%s*]*>\n?", "", 1)

source = source:gsub("]*>%s*$", "", 1)

return func(source, frame.args[2], frame.args[3])

end

end

return {

read_wiki_input = read_wiki_input,

}

end)

register('day21', function(myrequire)

-- DAY 21 --

local l = myrequire('llpeg')

local PriorityQueue = myrequire('pqueue')

local BigNum = myrequire('bignum')

local read_wiki_input = myrequire('util').read_wiki_input

local compat = myrequire('advent.compat')

local TRACK_PATH = false

-- PARSING --

local Block = {}

Block.__index = Block

local Rock = setmetatable({rock=true,type="#"}, Block)

Rock.__index = Rock

local Plot = setmetatable({plot=true,type="."}, Block)

Plot.__index = Plot

local Start = setmetatable({start=true,type="S"}, Plot)

Start.__index = Start

function Rock:__tostring() return "#" end

function Plot:__tostring()

if self.reached then return "O" end

if self.start then return "S" end

return "."

end

Start.__tostring = Plot.__tostring -- metamethods don't inherit (oddly)

local nl = l.P"\n"

local function make_block(type)

if type=='#' then return setmetatable({}, Rock) end

if type=='.' then return setmetatable({}, Plot) end

if type=='S' then return setmetatable({}, Start) end

error("unknown block type: "..type)

end

local patt = l.P{

"Graph",

Graph = l.Ct( l.V"Row" * (nl^1 * l.V"Row")^0 * nl^0) * -1,

Row = l.Ct( l.V"Block"^1 ),

Block = l.S".#S" / make_block,

}

local Graph = {}

Graph.__index = Graph

local function parse(source, addWarp)

local ast, errlabel, pos = patt:match(source)

if not ast then

error(string.format("Error at pos %s label '%s'", pos, errlabel))

end

--print('Parsed with success!')

return Graph:new(ast):link(addWarp)

end

-- Part 1 --

function Graph:new(data)

return setmetatable({ data=data }, self)

end

function Graph:at(row,col,default)

return ((self.data or {})[row] or {})[col] or default

end

function Graph:foreach(func) -- r,c,val actually

for r,row in pairs(self.data or {}) do

for c,val in pairs(row) do

func(r,c,val)

end

end

end

local function compare_rc(a, b)

if a.r < b.r then return true end

if a.r > b.r then return false end

if a.c < b.c then return true end

if a.c > b.c then return false end

-- elements are equal

return false

end

function Graph:hash(func)

local accum = {}

local coords = {}

self:foreach(function(r,c,val)

table.insert(coords, {r=r,c=c,val=func(val)})

end)

table.sort(coords, compare_rc)

for _,v in ipairs(coords) do

table.insert(accum, string.format("%d,%d,%s;", v.r,v.c, v.val))

end

return table.concat(accum)

end

function Graph:set(row,col,val)

if self.data == nil then

self.data = {}

end

if self.data[row] == nil then

self.data[row] = {}

end

self.data[row][col] = val

end

function Graph:setDefault(row,col,valfunc)

local val = self:at(row, col)

if val ~= nil then return val end

if type(valfunc) == 'function' then

val = valfunc()

else

val = valfunc

end

self:set(row, col, val)

return val

end

function Graph:rowN()

return #(self.data)

end

function Graph:colN()

return #(self.data[1])

end

function Graph:print()

for r,row in ipairs(self.data) do

for c,val in ipairs(row) do

if val == nil then

val = " "

end

io.write(tostring(val))

end

io.write("\n")

end

end

function Graph:link(addWarp)

for r=1,self:rowN() do

for c=1,self:colN() do

local sp = self:at(r,c)

sp.r, sp.c = r,c

if r > 1 then

sp.n = self:at(r-1,c)

elseif addWarp then

sp.n = { warp=self:at(self:rowN(), c), r=-1, c=0 }

end

if c > 1 then

sp.w = self:at(r,c-1)

elseif addWarp then

sp.w = { warp=self:at(r, self:colN()), r=0, c=-1 }

end

if r < self:rowN() then

sp.s = self:at(r+1,c)

elseif addWarp then

sp.s = { warp=self:at(1,c), r=1, c=0 }

end

if c < self:colN() then

sp.e = self:at(r,c+1)

elseif addWarp then

sp.e = { warp=self:at(r,1), r=0, c=1 }

end

if sp.start == true then self.start = {r=r, c=c} end

end

end

return self

end

local directions = { "n", "e", "s", "w" }

function Graph:search1(start, steps)

local Q = PriorityQueue()

local sp = self:at(start.r, start.c)

local reachCount = 0

local parity = steps % 2

sp.reached = true

Q:put({sp=sp,steps=0}, 0)

while not Q:empty() do

local state = Q:pop()

if state.steps % 2 == parity then reachCount = reachCount + 1 end

if state.steps < steps then

local nextSteps = state.steps + 1

for _,d in ipairs(directions) do

local next = state.sp[d]

if next ~= nil and not next.rock and not next.reached then

next.reached = true

Q:put({sp=next,steps=nextSteps}, nextSteps)

end

end

end

end

return reachCount

end

local function part1(source, amt)

local graph = parse(source)

--graph:print()

--print()

local n = graph:search1(graph.start, amt or 64)

--graph:print()

return n

end

-- PART 2 --

local function sortedKeys(tbl)

local sorted = {}

for k,_ in pairs(tbl) do

table.insert(sorted, k)

end

table.sort(sorted)

return sorted

end

function Graph:search2(start, steps)

local sp = self:at(start.r, start.c)

local parity = steps % 2

local metagraph = Graph:new()

local function metagraphAt(mr, mc)

return metagraph:setDefault(mr, mc, function()

return { r=mr, c=mc, g=Graph:new() }

end)

end

local function setReached(meta, sp)

meta.g:set(sp.r, sp.c, true)

end

local function reached(meta, sp)

return meta.g:at(sp.r, sp.c) ~= nil

end

local function hash(frontier)

local accum = {}

local coords = {}

for _,v in ipairs(frontier) do

-- ignore meta, ignore steps

table.insert(coords, {r=v.sp.r,c=v.sp.c})

end

table.sort(coords, compare_rc)

for _,v in ipairs(coords) do

table.insert(accum, string.format("%d,%d;", v.r, v.c))

end

return table.concat(accum)

end

local memo = {}

local id = 1

local firstLoop = nil

local function doOne(currentFrontier, metaNext, seen)

local key = hash(currentFrontier)

if memo[key] ~= nil then

--print("seen", currentFrontier[1].meta.r, currentFrontier[1].meta.c)

if firstLoop == nil then

firstLoop = currentFrontier[1].steps

--print("First loop", firstLoop)

end

else

memo[key] = { id=id }

id = id + 1

end

local reachCount = 0

local nextFrontier = {}

for i=1,#currentFrontier do

local state = currentFrontier[i]

if state.steps % 2 == parity then reachCount = reachCount + 1 end

if state.steps < steps then

local nextSteps = state.steps + 1

for _,d in ipairs(directions) do

local nextmeta = state.meta

local next = state.sp[d]

if next ~= nil and next.warp ~= nil then

local mr, mc = nextmeta.r + next.r, nextmeta.c + next.c

nextmeta = metagraphAt(mr, mc)

next = next.warp

end

if next ~= nil and not next.rock and not reached(nextmeta, next) then

setReached(nextmeta, next)

-- collect the 'nextFrontier' by metablock

local nextFrontier = metaNext:setDefault(nextmeta.r, nextmeta.c, {})

table.insert(nextFrontier, {meta=nextmeta,sp=next,steps=nextSteps})

end

end

end

end

if memo[key].reachCount == nil then

memo[key].reachCount = reachCount

else

memo[key].bad = true

end

seen[memo[key].id] = (seen[memo[key].id] or 0) + 1

return reachCount

end

local reachCount = 0

local currentFrontier = Graph:new()

currentFrontier:set(0,0,{ {meta=metagraphAt(0,0),sp=sp,steps=0} })

setReached(metagraphAt(0,0), sp)

--local last = {0,0,0,0,0,0}

local bigSum = {}

repeat

local count=0

local metaNext = Graph:new()

local seen = {}

local steps = nil

currentFrontier:foreach(function(mr,mc,frontier)

reachCount = reachCount + doOne(frontier, metaNext, seen)

count = count + 1

steps = frontier[1].steps

end)

currentFrontier = metaNext

-- print status

if false then

local buf = {}

for _,v in ipairs(sortedKeys(seen)) do

table.insert(buf, string.format("%d=%d ", v, seen[v]))

end

--print("Steps", steps, reachCount, table.concat(buf))

end

if false then

if (steps % (2*131)) == 65 then

print("Steps", steps, reachCount)

end

local era = compat.idiv(steps, 131)

if bigSum[era] == nil then bigSum[era] = {} end

for i,v in pairs(seen) do

bigSum[era][i] = (bigSum[era][i] or 0) + v

end

if (steps % 131) == 130 and false then

local buf = {}

for _,v in ipairs(sortedKeys(bigSum[era])) do

table.insert(buf, string.format("%d=%d ", v, bigSum[era][v]))

end

--print(table.concat(buf))

end

end

until count == 0

return reachCount

end

--[[

We have a cycle of length 131: (first repeated occurrence of a block is at step 197)

Steps 131 392=1 393=1 394=1 395=1

Steps 262 392=1 393=1 394=1 395=1 1436=1 1437=1 1438=1 1439=1

Steps 393 392=1 393=1 394=1 395=1 1436=2 1437=2 1438=2 1439=2

Steps 524 392=1 393=1 394=1 395=1 1436=3 1437=3 1438=3 1439=3

And also at points in the middle of the cycle:

Steps 300 692=2 693=1 694=2 695=1 696=1 697=2 698=1 699=2 1588=1 1589=1 1590=1 1591=1

Steps 431 692=3 693=1 694=3 695=1 696=1 697=3 698=1 699=3 1588=2 1589=2 1590=2 1591=2

Steps 562 692=4 693=1 694=4 695=1 696=1 697=4 698=1 699=4 1588=3 1589=3 1590=3 1591=3

Look at the total reach count at correspondings points in the

cycle. NOTE THAT THE PARITY FLIPS EACH TIME because 131 is odd, so

we should only compare "odd" cycles with "odd" cycles. Be careful

you give the desired ending length when you run the program, or

you'll get sums for the wrong parity!

We want 26501365 steps, which is 101150 * 2*131 + 65!

Luckily, our trick still works!

Steps 327 94909 212122 121080

Steps 589 307031 333202 121080

Steps 851 640233 454282

Steps 1113 1094515

Steps 1375 1669877

Step N*2*131+65 = a*N^2 + b*N + c; solve

N=1 => 94909 = a + b + c 3a + b = 212122 2a=121080 a=60540

N=2 => 307031 = 4a + 2b + c 5a + b = 333202 b=30502

N=3 => 640233 = 9a + 3b + c c=3867

After N*2*131+65 steps, reaches: 60540 * N^2 + 30502 * N + 3867

Solve for N=23, 6091 steps: 32731073

Solve for N=101150 => 619407349431167

]]--

local function part2(source, amt)

local graph = parse(source, true) -- with warps

local N1 = graph:search2(graph.start, 1*2*131+65)

local N2 = graph:search2(graph.start, 2*2*131+65)

local N3 = graph:search2(graph.start, 3*2*131+65)

local N2mN1 = N2 - N1 -- 212122

local N3mN2 = N3 - N2 -- 333202

local a = compat.idiv(N3mN2 - N2mN1, 2)

local b = N2mN1 - 3*a

local c = N1 - a - b

--print(N1, N2, N3, a, b, c)

local N = compat.idiv(BigNum:new(amt) - 65, 2*131)

return N*N*a + N*b + c

end

--[[ CLI ] ]--

local source = io.input("day21.input"):read("*a")

print('Plots:', part1(source, 6))

-- print('Infinite Plots:', part2(source, 6)) -- 44

-- print('Infinite Plots:', part2(source, 10)) -- 110

-- print('Infinite Plots:', part2(source, 50)) -- 2268

-- print('Infinite Plots:', part2(source, 100)) -- 8993

-- print('Infinite Plots:', part2(source, 500)) -- 221398

-- print('Infinite Plots:', part2(source, 1000)) -- 884098

-- print('Infinite Plots:', part2(source, 5000)) -- 22056540

-- print('Infinite Plots:', part2(source, 64)) -- 3751

-- print('Infinite Plots:', part2(source, 64+131)) --33904

-- print('Infinite Plots:', part2(source, 64+2*131)) -- 94327

-- print('Infinite Plots:', part2(source, 64+5*131)) -- 457216

-- print('Infinite Plots:', part2(source, 64+10*131)) -- 1667431

-- print('Infinite Plots:', part2(source, 64+20*131)) -- 6358111

-- print('Infinite Plots:', part2(source, 64+30*131)) -- 14075791

print('Infinite Plots:', part2(source, 26501365)) -- 3751

--[ [ END CLI ]]--

return {

part1 = read_wiki_input(part1),

part2 = read_wiki_input(part2),

}

end)

local modules = {}

modules['bit32'] = require('bit32')

modules['string'] = require('string')

modules['strict'] = {}

modules['table'] = require('table')

local function myrequire(name)

if modules[name] == nil then

modules[name] = true

modules[name] = (builders[name])(myrequire)

end

return modules[name]

end

return myrequire('day21')

end)()