advent-of-code/12022/aoc12022.html

2421 lines
83 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>🥭 &mdash; aoc12022</title>
<style>
a[rel=external]:after{
content:"↝";
}
a[href^="#"]::after{ /*links internos*/
content:"↕";
}
a{
color: #300;
font-style:italic;
}
body{
color: #200;
background-color: #320;
}
pre{
background-color: #640;
color: #ec0;
padding:1em;
overflow-x:auto;
font-size:larger;
}
header,nav,main,footer{
background-color:#c93;
max-width:50em;
padding:1em;
}
main{
max-width:50em;
}
section,nav{
border-top: solid #ec0;
}
h1{
border-left: solid 10px #ec0;
padding-left:0.5em;
}
h2{
border-left: solid 2px #ec0;
padding-left:1em;
}
article > h1{
border-left: double 5px #ec0;
}
footer p{
font-size:small;
}
</style>
</head>
<body>
<header>
<h1>aoc12022</h1>
<p>brief documentation of my journey solving <a href="https://adventofcode.com/2022" rel="external" target="_blank">advent of code (1)2022</a>.</p>
<p>my languages of choice are <a href="https://fennel-lang.org" rel="external" target="_blank">fennel</a> and <a href="https://lua.org" rel="external" target=_blank>lua</a>. it's my first time using the former and my first time using a lisp. additionally, i want to become more proficient using lua. then both languages complement each other during this process.</p>
<p>post-script: at the end i mostly worked with lua only.</p>
</header>
<nav id="top">
<ul>
<li><a href="#01">01</a></li>
<li><a href="#02">02</a></li>
<li><a href="#03">03</a></li>
<li><a href="#04">04</a></li>
<li><a href="#05">05</a></li>
<li><a href="#06">06</a></li>
<li><a href="#07">07</a></li>
<li><a href="#08">08</a></li>
<li><a href="#09">09</a></li>
<li><a href="#10">10</a></li>
<li><a href="#11">11</a></li>
<li><a href="#12">12</a></li>
<li><a href="#13">13</a></li>
<li><a href="#14">14</a></li>
<li><a href="#15">15</a></li>
<li><a href="#17">17</a></li>
<li><a href="#18">18</a></li>
<li><a href="#20">20</a></li>
<li><a href="#21">21</a></li>
<li><a href="#23">23</a></li>
<li><a href="#25">25</a></li>
</ul>
</nav>
<main>
<section id="01">
<h1>01 calorie counting</h1>
<p>
<a href="https://adventofcode.com/2022/day/1" rel="external" target="_blank">Day 1: Calorie Counting</a>
</p>
<h2>01 fennel</h2>
<h3>the first one</h3>
<p>this is my final<sup>[citation needed]</sup>(update: <em>not</em> final, see below) solution using fennel, after solving the puzzles with lua first (see (even more) below).</p>
<pre>
<code>
(io.input "input")
(var calories [0])
(each [line (io.lines)]
(if (= line "") (table.insert calories 0)
(let [i (length calories)]
(tset calories i (+ (tonumber line) (. calories i))))))
; sort so that greater numbers go first
(table.sort calories (λ [a b] (&gt; a b)))
(print "part 1" (. calories 1))
(print "part 2" (let [c calories] (+ (. c 1) (. c 2) (. c 3))))
</code>
</pre>
<p>after watching <a href="https://0000.lol" rel="external" target="_blank">aw's stream</a> i realized the solution is inefficient in the sense that use a whole &ldquo;array&rdquo; to save the results, and that i use the <code>table.sort</code> function. instead, i could have calculated the maximum(s) on the fly, without needing to store anything else</p>
<p><strike>i'm letting that pass for now because i'm here to learn about the affordances of the language(s), and i see knowing the standard library and becoming fluent with the use of tables as part of that.</strike></p>
<h3>update</h3>
<p><em>same day update</em>: i didn't let the previous notes pass, and i managed to solve everything &ldquo;on the fly&rdquo; by using a small table and the functions to insert or remove elements at arbitrary positions (<code>table.insert</code> or <code>table.remove</code> respectively), that default to acting at the end, like a stack.</p>
<pre>
<code>
(io.input "input")
(local n 3)
(var max [0 0 0]) ; greatest is position 1, then 2, last 3
(var calories-sum 0)
(each [line (io.lines)]
(let [num (tonumber line)]
(if (not num)
(let [ s calories-sum
; count to how many "max" the new sum is greater than
greater-than
(accumulate [sum 0 _ m (ipairs max)]
(+ sum (if (&gt; s m) 1 0)))]
(set calories-sum 0) ; restart accumulator
(when (&gt; greater-than 0)
; insert new max and remove last element
(table.insert max (- (+ n 1) greater-than) s)
(table.remove max)))
(set calories-sum (+ calories-sum num)))))
(print "part 1" (. max 1))
(print "part 2" (accumulate [sum 0 _ m (ipairs max)] (+ sum m)))
</code>
</pre>
<p>this took a while. it seems it is not necesarily faster, but it was fun to build. i especially liked arriving at the use of <code>accumulate</code> to figure out to how many maximums the new sum is.</p>
<p>i decided to use that strategy to avoid having to do 1) a &ldquo;manual&rdquo; comparison between the new sum and each of the maximum values, 2) a &ldquo;manual&rdquo; swapping around of the maximum values.</p>
<h2>01 lua</h2>
<p>i followed the drive to go fast when the puzzle was unveiled, so i used lua first. it seems i feel very comfortable in structured, imperative programming.</p>
<h3>first version</h3>
<p>this is a cleaned up version of what i used to obtain the results. i slightly modified it after doing the fennel version, especially in the use of the length operator <code>#</code> instead of an accumulator.</p>
<pre>
<code>
io.input("input")
calories = {0}
i = #calories
for line in io.lines() do
if line ~= "" then
calories[i] = calories[i] + tonumber(line)
else
table.insert(calories, 0)
i = #calories
end
end
--
maxa, maxb, maxc = 0, 0, 0
for i, c in ipairs(calories) do
if c&gt;maxa then
maxc = maxb
maxb = maxa
maxa = c
elseif c&gt;maxb then
maxc = maxb
maxb = c
elseif c&gt;maxc then
maxc = c
end
end
part1 = maxa
part2 = maxa+maxb+maxc
print("part 1", part1)
print("part 2", part2)
</code>
</pre>
<p>the &ldquo;inefficiency&rdquo; is more evident here, as i use two loops to go through similar data twice. additionally, at this point i didn't remember there was a way of sorting the &ldquo;array&rdquo;, so i did a &ldquo;manual&rdquo; comparison.</p>
<h3>second version</h3>
<p>once i saw there was a <code>table.sort</code> function i used it instead. only the second part changed; the second <code>for</code> loop and what comes next was replaced with the following:</p>
<pre>
<code>
table.sort(calories, function(a,b) return (a&gt;b) end)
part1 = calories[1]
part2 = calories[1]+calories[2]+calories[3]
print("part 1", part1)
print("part 2", part2)
</code>
</pre>
<h2>lessons</h2>
<p>some practical stuff i take with me:</p>
<ul>
<li>even though lua is &ldquo;small&rdquo;, it does have a handy <code>table.sort</code> function. it's worth keeping the standard library in mind (and in sight, first).</li>
<li>regarding efficiency, it makes sense to think through the puzzle a little bit more before starting to write code. not because lua has tables i have to use them.</li>
<li><em>update</em>: it's fun to figure out if some aspects can be solved using either <code>accumulate</code> (note to self, similar to &ldquo;fold&rdquo;) or <code>icollect</code> (note to self, similar to &ldquo;map&rdquo;)</li>
</ul>
<p>interestingly, i think that the fennel code <em>looks</em> way more nicer than the lua one. i'm starting to see the aesthetic appeal of lisps.</p>
<p>for the record<sup>(?)</sup>, i woke up in order to be ready at 6am, i did the first part in less than 5 minutes, and i was done with the second one in less than 15 minutes (total). let's see how that changes as i get more proficient and as the puzzles get more difficult. also, let's see what happens with my willingness to wake up that early to be &ldquo;on time&rdquo; for each new puzzle.
</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="02">
<h1>02 rock paper scissors</h1>
<p>
<a href="https://adventofcode.com/2022/day/2" rel="external" target="_blank">Day 2: Rock Paper Scissors</a>
</p>
<h2>02 lua</h2>
<p>for this puzzle i thought the simplest way to go would be to use a <em>lookup table</em> (i.e. hard-code the data), especially because of, you know, lua <em>tables</em>.</p>
<p>i'm glad i decided this instead of wrangling with conditionals and so on, even more when arriving at the second part.</p>
<pre>
<code>
io.input("input")
-- Rock, paper, scissors
own_scores = { X=1, Y=2, Z=3 }
game_scores = { X = { A=3, B=0, C=6},
Y = { A=6, B=3, C=0},
Z = { A=0, B=6, C=3}}
-- lose, draw, win
own_scores_2 = { X=0, Y=3, Z=6 }
game_moves = { X = { A=3, B=1, C=2}, -- lose
Y = { A=1, B=2, C=3}, -- draw
Z = { A=2, B=3, C=1}} -- win
score = 0
score_2 = 0
for line in io.lines() do
other, you = string.match(line, "(%a+) (%a+)")
score = own_scores[you] + game_scores[you][other] + score
score_2 = own_scores_2[you] + game_moves[you][other] + score_2
end
print("part 1", score)
print("part 2", score_2)
</code>
</pre>
<p>not much to see here, i guess!</p>
<p>worth mentioning is that several times i saw the tables and wanted to &ldquo;simplify&rdquo; them. for example, <code>own_scores_2</code> would correspond to the result of <code>own_scores</code> minus 1 and then times 3 (?). i decided to not do it; it doesn't make the code clearer and probably requires slightly more operations to work.</p>
<h2>02 fennel</h2>
<p>i doubted in doing the fennel version as it would be too straight of a transcription, but then i decided to go for it to compare the notation of tables there.</p>
<pre>
<code>
(io.input "input")
; rock, paper, scissors
(local own-scores-1 {:X 1 :Y 2 :Z 3})
(local game-scores {:X {:A 3 :B 0 :C 6}
:Y {:A 6 :B 3 :C 0}
:Z {:A 0 :B 6 :C 3}})
; lose, draw, win
(local own-scores-2 {:X 0 :Y 3 :Z 6})
(local game-moves {:X {:A 3 :B 1 :C 2}
:Y {:A 1 :B 2 :C 3}
:Z {:A 2 :B 3 :C 1}})
(var score-1 0)
(var score-2 0)
(each [line (io.lines)]
(let [(other you) (string.match line "(%a+) (%a)")
move-score (. own-scores-1 you)
round-score (. (. game-scores you) other)
own-score-2 (. own-scores-2 you)
move-score-2 (. (. game-moves you) other)]
(set score-1 (+ score-1 move-score round-score))
(set score-2 (+ score-2 own-score-2 move-score-2))))
(print "part 1" score-1)
(print "part 2" score-2)
</code>
</pre>
<p>i feel that in this case the code gets more verbose; i tried to clean it up a little bit by using locals for the intermediate results from the tables.</p>
<h2>lessons</h2>
<p>i can see myself several years ago not wanting to use lookup tables. here i am now, and i'm glad about it.</p>
<p>also, lua tables are nice!</p>
<p>regarding time, i did wake up early to solve this, with a little bit of initial struggle. i had the first answer in around 14 minutes, and i was done in around 21 total. maybe i was &ldquo;slow&rdquo; writing the tables, but i would probably have been slower writing the conditions.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="03">
<h1>03 rock paper scissors</h1>
<p>
<a href="https://adventofcode.com/2022/day/3" rel="external" target="_blank">Day 3: Rucksack Reorganization</a>
</p>
<h2>03 lua</h2>
<p>this is the code i used to finish the puzzles and get the stars!
</p>
<pre>
<code>
io.input("input")
function getPriority(c)
if c&gt;='a' and c&lt;='z' then
return string.byte(c) - string.byte('a') + 1
else
return string.byte(c) - string.byte('A') + 27
end
end
-- for part 1
local sum = 0
local linecount = 0
-- for part 2
local common = {}
local sumBadges = 0
for line in io.lines() do
local h = {}
local i = 0
local repeated = nil
for c in string.gmatch(line, "%a") do
-- part 1
if i&lt;#line/2 then
h[c] = (h[c] or 0) + 1
elseif not repeated and h[c] then
repeated = c
end
i = i + 1
-- part 2
if linecount==0 then
common[c] = 1
elseif common[c] and linecount==1 then
common[c] = 2
elseif common[c]==2 then -- 2
common[c] = 3
sumBadges = sumBadges + getPriority(c)
end
end
-- part 1
local priority = getPriority(repeated)
sum = sum + priority
-- part 2
if linecount==2 then common = {} end
linecount = (linecount + 1)%3
end
print("part 1", sum)
print("part 2", sumBadges)
</code>
</pre>
<p>the code got the work done but i feel there would be ways to make it even more compact. especially for the second part, i'm not super happy about how i managed to do it &mdash; but i knew being this explicit would help me in not having weird edge cases.</p>
<p>i won't do the fennel version for the moment, but it'd be interesting to try using &ldquo;functional niceties&rdquo; for that.</p>
<h2>lessons</h2>
<p>this was a nice opportunity to practice an apparently common lua idiom, the use of <code>or</code> to use the value of a variable or set one if it wasn't set. additionally, i got to use <code>string.byte</code> and <code>string.gmatch</code>; both seem handy and i had to look them up to make sure they existed and to make sure how they were used as an iterator (in the latter case).</p>
<p>it seems i like (and i'm used to) working at a somewhat &ldquo;low&rdquo; level, getting things done managing arrays, counters, and stuff like that. hopefully the fennel attempts will help me in trying another way of thinking.</p>
<p>this time i needed around 15 minutes for the first part, and i was done in 35 minutes total. i could have used more sleeping time, but yesterday afternoon and night was great.</p>
<p>i take with me the feeling of being alright with not having the most beautiful, concise code. i'm having fun!</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="04">
<h1>04 camp cleanup</h1>
<p>
<a href="https://adventofcode.com/2022/day/4" rel="external" target="_blank">Day 4: Camp Cleanup</a>
</p>
<h2>04 fennel</h2>
<p>this <strike>is</strike> was my final solution using fennel, written after the lua version below.</p>
<code>
<pre>
(io.input "input")
(var sum1 0)
(var sum2 0)
(each [line (io.lines)]
(let [(a b c d) (string.match line "(%d+)-(%d+),(%d+)-(%d+)")
ri1 (tonumber a) rf1 (tonumber b)
ri2 (tonumber c) rf2 (tonumber d)]
; part 1
(when (or (and (&gt;= ri1 ri2) (&lt;= rf1 rf2))
(and (&gt;= ri2 ri1) (&lt;= rf2 rf1)))
(set sum1 (+ sum1 1)))
; part 2
(when (or (and (&gt;= rf1 ri2) (&lt;= ri1 ri2))
(and (&gt;= rf2 ri1) (&lt;= ri2 ri1)))
(set sum2 (+ sum2 1)))))
(print "part 1" sum1)
(print "part 2" sum2)
</pre>
</code>
<p>i'm still using the approach of iterating line by line and increasing the counters inside; i wonder how would it be to use the <code>accumulate</code> iterator in cases like this.</p>
<p>this is basically a transcription of the lua program below, but i like it more do to its compactness.</p>
<h2>04 lua</h2>
<p>this is the slightly cleaned up program i used to solve the puzzle and get the stars:</p>
<code>
<pre>
io.input("input")
local sum = 0
local sum2 = 0
for line in io.lines() do
local ri1, rf1, ri2, rf2 = string.match(line, "(%d+)-(%d+),(%d+)-(%d+)")
ri1=tonumber(ri1)
rf1=tonumber(rf1)
ri2=tonumber(ri2)
rf2=tonumber(rf2)
-- part 1
if (ri1&gt;=ri2 and rf1&lt;=rf2) or (ri2&gt;=ri1 and rf2&lt;=rf1) then
sum = sum + 1
end
-- part 2
if (rf1&gt;=ri2 and ri1&lt;=ri2) or (rf2&gt;=ri1 and ri2&lt;=ri1) then
sum2 = sum2 + 1
end
end
print("part 1", sum)
print("part 2", sum2)
</pre>
</code>
<p>note the use of the <code>tonumber</code> function, that &ldquo;bit&rdquo; me and made me have a couple of wrong answers. i erronously thought that by capturing a set of digits with the <code>(%d+)</code> pattern i was going to get a number. i guess i got spoiled by <code>awk</code> (?). in any case, i was able to figure that out with the <code>typeof</code> function.</p>
<p>actually, for a moment i used the following approach to get the numbers. i could have stayed with the table and not use local, named variables, but i preferred to use the named variables to not get lost in the comparisons.</p>
<code>
<pre>
local r = {}
for n in string.gmatch(line,"%d+") do
table.insert(r, tonumber(n))
end
local ri1=r[1]
local rf1=r[2]
local ri2=r[3]
local rf2=r[4]
</pre>
</code>
<h2>updates</h2>
<h3>conditionals</h3>
<p>
after watching <a href="https://0000.lol" rel="external" target="_blank">aw's stream</a> and looking at <a href="https://git.alexwennerberg.com/aoc-forth/file/2022/04.fs.html" rel="external" target="_blank">his code for day 4 in UF</a> i realized my conditions were redundant in part two.</p>
<p>in fennel, it was enough with doing:</p>
<code>
<pre>
(when (and (&gt;= rf1 ri2) (&lt;= ri1 rf2))
(set sum2 (+ sum2 1)))
</pre>
</code>
<p>and respectively, in lua:</p>
<code>
<pre>
if (rf1&gt;=ri2 and ri1&lt;=rf2) then
sum2 = sum2 + 1
end
</pre>
</code>
<h3>mapping</h3>
<p><a href="https://sonomu.club/@flavigula" target="_blank" rel="external">@flavigula@sonomu.club</a> illuminated me and allowed me to see how i could use both the threading macro <code>-&gt;</code> and the <code>table.pack</code> function to convert all the arguments in the form of string, to numbers, and assing them directly to the named variables i had.</p>
<p>the following would be the actual final<sup>citation needed</sup> version using fennel:</p>
<code>
<pre>
(io.input "input")
(var sum1 0)
(var sum2 0)
(each [line (io.lines)]
(let [pattern "(%d+)-(%d+),(%d+)-(%d+)"
[ri1 rf1 ri2 rf2]
(icollect [_ n
(-&gt; line (string.match pattern) (table.pack) (ipairs))]
(tonumber n))]
(when (or (and (&gt;= ri1 ri2) (&lt;= rf1 rf2))
(and (&gt;= ri2 ri1) (&lt;= rf2 rf1)))
(set sum1 (+ sum1 1)))
(when (and (&gt;= rf1 ri2) (&lt;= ri1 rf2))
(set sum2 (+ sum2 1)))))
(print "part 1" sum1)
(print "part 2" sum2)
</pre>
</code>
<p>note (to self?) that the threading macro part is equivalent to:</p>
<code>
<pre>
;(-&gt; ...) above equivalent to:
(ipairs (table.pack (string.match line pattern)))
</pre>
</code>
<h2>lessons</h2>
<p>in both of these cases, i was left wondering if i could use the return values of <code>string.match</code> and &ldquo;pack&rdquo; them in a table so that then i could <em>map</em> the <code>tonumber</code> function.</p>
<p>now i know that when parsing string to numbers, i should always use <code>tonumber</code> to make sure. interestingly, in previous days i had used it although i had noticed it didn't seem to make a difference. note that today the problem was apparent when reading numbers greater than or equal to 10 (i.e., with more than 1 digit), in the actual input.</p>
<p>for a moment i thought of starting today's solution with fennel directly, but then again i preferred to go with lua in order to solve it &ldquo;fast&rdquo;. probably, when puzzles get more complicated and i need more time to think through them, i'll start preferring using fennel.</p>
<p>today i finished part 1 in a little bit more than 18 minutes, and more than half of those were about figuring out the error that needed the <code>tonumber</code> function. i was done in a little bit more than 26 minutes total; for part 2 i was also making some mistakes with the comparisons, that i could identify since testing with the sample cases.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="05">
<h1>05 supply stacks</h1>
<p>
<a href="https://adventofcode.com/2022/day/5" rel="external" target="_blank">Day 5: Supply Stacks</a>
</p>
<h2>05 lua</h2>
<p>this is the code i used to solve today's puzzles and get the stars! this was some intense parsing.
</p>
<p>for part 2 i originally had another file as i didn't want to mess up my answer for part 1, but for posting it here i joined them both together as the differences were minimal.</p>
<code>
<pre>
io.input("input")
stacks = {}
stacks2 = {}
instructions = {}
count = 0
-- parsing
for line in io.lines() do
if string.find(line, "%[%a%]") then
local nstacks = #line//4 + 1 -- 1 5 9 13
for i = 1,nstacks do
if count == 0 then
stacks[i] = {}
stacks2[i] = {}
end
local index = 1 + (i-1)*4 -- 1, 5, 9
local s = string.sub(line, index, index+3)
local t = string.match(s, "%[(%a)%]")
-- insert crate at the bottom:
table.insert( stacks[i], 1, t )
table.insert( stacks2[i], 1, t )
end
elseif string.find(line, "^move") then
local pattern = "^move (%d+) from (%d+) to (%d+)$"
local tt = table.pack(string.match(line, pattern))
local t = {}
for j, v in ipairs(tt) do t[j] = tonumber(v) end
table.insert(instructions, t)
end
count = count + 1
end
-- moves
for _, t in ipairs(instructions) do
q, src, dst = table.unpack(t)
-- part 1
for n = 1,q do
local crate = table.remove( stacks[src] )
table.insert( stacks[dst], crate )
end
-- part 2
local crates = {}
for n = 1,q do
crates[q-n+1] = table.remove( stacks2[src] )
end
for n = 1,q do
table.insert( stacks2[dst], crates[n] )
end
end
-- result
local result = ""
for _, stack in ipairs(stacks) do
result = result .. stack[#stack]
end
print("part 1", result)
result = ""
for _, stack in ipairs(stacks2) do
result = result .. stack[#stack]
end
print("part 2", result)
</pre>
</code>
<p>later on i realized i could have done the <em>moves</em> part inside the main parsing loop as well, getting rid of the <code>instructions</code> table. it's such a &ldquo;minor&rdquo; change that i'm leaving it as is.</p>
<p>for parsing debugging purposes i wrote the following function that i left out above because it's not used to arrive at the answer:</p>
<code>
<pre>
function printStacks()
for i,stack in ipairs(stacks) do
local s = ""
for _,j in ipairs(stack) do
s = s .. j
end
print(i, s)
end
end
</pre>
</code>
<h2>lessons</h2>
<p>i considered starting working on the puzzle using fennel, but now i found it a little bit overwhelming not because of wanting to go fast but because of the data handling i envisioned would happen (and yes, because i thought it would take me a lot of time).</p>
<p>i'm happy i was able to imagine how to solve this with lua, and also because i managed to keep calm. i liked how i was able to clean up my code and ideas on-the-fly. for example, first i was just storing the first kind of lines to parse them later when i &ldquo;knew for sure&rdquo; the amount of stacks, i.e. because of the numbering. then i realized i could know that amount from the beginning and therefore parse the crates in the first pass.</p>
<p>i also enjoyed using the table library functions to manage the &ldquo;literal&rdquo; stacks in part 1, and then using some array wrangling for the queues in part 2. the parsing was what took me the longest; once i had the data i found the moves to be easy.</p>
<p>for part 1 i was done in a little bit more than 57 minutes (!), and i was done with everything in 1:02 hours. let's see what comes in the next days... meanwhile, time to &ldquo;start&rdquo; the day!</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="06">
<h1>06 tuning trouble</h1>
<p>
<a href="https://adventofcode.com/2022/day/6" rel="external" target="_blank">Day 6: Tuning Trouble</a>
</p>
<h2>06 lua</h2>
<p>this was my solution using lua:</p>
<code>
<pre>
io.input("input")
local s = io.read("a")
function endOfMarker( n ) -- n is the window size
local i=1
repeat
local window = string.sub(s,i,i+n-1)
local repeats = false
local chars = {}
for j=1,n do
local c = string.sub(window,j,j)
if chars[c] then
repeats = true
break
else chars[c] = true
end
end
i = i + 1
until not repeats or i&gt;#s
return i+n-2
end
print("part 1", endOfMarker(4))
print("part 2", endOfMarker(14))
</pre>
</code>
<h2>lessons</h2>
<p>in the first part i felt a little bit slow; probably because i wanted to use the <code>string.sub</code> function in order to get used to its notation (first index, and last index of substring). at the end that approach was nice because i could easily extend it for part 2.</p>
<p>this was also the first time i use a <code>repeat until</code> loop. i observed in the opposite direction the confusion that some of my students tended to have when having more than one condition to signal the end of a (<code>while</code>) loop, i.e. should there be an <code>and</code> or an <code>or</code>.</p>
<p>this time i started but then i didn't feel like replicating the same behavior in fennel, as i feel there would be a more <em>functional</em> of solving this. looking forward to an example of this!</p>
<p>regarding times, for the first part i finished in a little bit more than 14 minutes, but then i was done at a little bit less than 17; and that included the re-writing of the code as a function that i could reuse for both parts. i'm still managing and enjoying waking up some minutes before the puzzle launch.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="07">
<h1>07 no space left on device</h1>
<p>
<a href="https://adventofcode.com/2022/day/7" rel="external" target="_blank">Day 7: No Space Left On Device</a>
</p>
<h2>07 lua</h2>
<p>today is one of those days when i saw the problem and thought &ldquo;what am i doing here&rdquo; at first. with a little bit more time i managed to start working on it.</p>
<p>in general i'm glad i'm using lua, but for this case i was especially glad that tables (and variables, for that matter) work the way they work; i.e. that i can point to the same table with different variables. i remember i had some problems last year with <code>awk</code> in situations like this, where it was not clear to me what it was doing.</p>
<p>the longest code so far!</p>
<code>
<pre>
io.input("input")
function createDir( dirname )
return { name = dirname, children = {}, localsize = 0}
end
function insertDirIntoWD( dirname )
-- if it already exists, return it
for _, d in ipairs( wd.children ) do
if d.name == dirname then return d end
end
-- print("inserting", dirname, "into", wd.name)
-- create it otherwise
local newdir = createDir(dirname)
table.insert( wd.children, newdir )
table.insert( dirs, newdir )
return newdir
end
-- initialize
root = createDir("/")
dirs = {root}
stack = {root}
wd = root -- working directory
local listing = false
for line in io.lines() do
if listing then
if string.find(line, "^%$") then
listing = false
else -- listing
local pattern = "^(.+) (.+)$"
local info, name = string.match( line, pattern )
if info == "dir" then
insertDirIntoWD( name )
else -- file
local size = tonumber(info)
wd.localsize = wd.localsize + size
end
end
end -- if listing
if not listing then
if string.find(line, "^%$ ls") then
listing = true
else -- change directory
local dirname = string.match(line, "^%$ cd (.+)$")
if dirname == "/" then
wd = root
stack = {root}
elseif dirname == ".." then
wd = table.remove( stack ) -- pop from stack
else -- go to named dir
table.insert( stack, wd ) -- push wd into stack
wd = insertDirIntoWD( dirname )
end -- if dirname
end -- if $ ls
end -- if not listing
end -- for line
function totalSize( dir )
local sum = dir.localsize
for i, d in ipairs( dir.children ) do
sum = sum + totalSize( d )
end
return sum
end
-- part 1
local result1 = 0
-- part 2
local total = totalSize(dirs[1])
local unused = 70000000 - total
local needed = 30000000 - unused
local minGreaterThanNeeded = total
for i, d in ipairs(dirs) do
local size = totalSize(d)
-- part 1
if size &lt;= 100000 then
result1 = result1 + size
end
-- part 2
if size &gt;= needed and size &lt; minGreaterThanNeeded then
minGreaterThanNeeded = size
end
end
print("part 1", result1)
print("part 2", minGreaterThanNeeded)
</pre>
</code>
<h2>lessons</h2>
<p>at first i was afraid about the traversal of the directory tree, and also about its data struture. then i realized it wouldn't be as difficult because of, as i said above, the way tables and variables work in lua.</p>
<p>i was able to have a normal list of directories, where each of them was a table containing a corresponding <code>children</code> table that could include &ldquo;pointers&rdquo; to some of the directories in the main list. in other languages i guess i could have managed using the directory names, their paths, or some other way of identifying each of them. here i was able to use the same variable.</p>
<p>this advantage extended to the use of the stack for the traversal of the directories when following the input. first i was thinking in having a stack of directory names or some kind of id, so that i was able to look back and find them in the directory list. then i realized i could just use the same variables pointing to the directories.</p>
<p>in summary, any &ldquo;directory&rdquo; table (including name, local size, and children) was in the directory list/table, could be present in the children table/list of another one, could be present in the traversal stack for the instructions, and could be assigned to the <code>wd</code> (working directory) variable. all of them as references to the same table!</p>
<p>regarding recursion for the <code>totalSize</code>, at first i also thought it was going to be more difficult, but i mananged alright. probably there are better ways of doing it, especially because right now i'm being &ldquo;naive&rdquo; and i'm calculating the total sizes of directories more than once. <strike>i'll leave that problem for puzzles further along.</strike> (i didn't, see below)</p>
<p>today i needed a little bit less than 59 minutes for part 1, and i was done in a total of 66 minutes. interestingly, this is my third best ranked performance so far, after 01 (second) and 02 (first). it was intense! but as always, it's rewarding to see that i could implement this in a relatively short time.</p>
<h2>update</h2>
<p>i realized i could use the same, foretold directory tables to hold the value of their total size whenever it's calculated.</p>
<p>the changes are minimal and only happen in the <code>totalSize</code> function:</p>
<code>
<pre>
function totalSize( dir )
if dir.totalsize then return dir.totalsize end
local sum = dir.localsize
for i, d in ipairs( dir.children ) do
sum = sum + totalSize( d )
end
dir.totalsize = sum
return sum
end
</pre>
</code>
<p>technically, and for clarity (?), i could have added a <code>totalsize=nil</code> in the <code>createDir</code> function. however, setting it to <code>nil</code> is the same as not setting it, so that's why i preferred to modify the <code>totalSize</code> function only.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="08">
<h1>08 treetop tree house</h1>
<p>
<a href="https://adventofcode.com/2022/day/8" rel="external" target="_blank">Day 8: Treetop Tree House</a>
</p>
<h2>08 lua</h2>
<p>this was intense! i went through several iterations. first i thought i'd use some recursive stuff like yesterday, only checking for the visibility of adjacent trees, but then i saw my approach wasn't alright: there could be a new, visible tree, next to a non-visible one.</p>
<p>part of me sees this code and wants to <em>optimize</em> it. however, i'm glad that with the approach i used at the end, integrating the solution for part 2 was almost straightforward.</p>
<p>here's the program!</p>
<code>
<pre>
io.input("input")
function addTree( r, c, height )
local visible = nil
if r==1 or c==1 or r==n or c==n then
visible = true
end
local tree = { h = height, v = visible, s = 1 }
trees[r][c] = tree
return tree
end
trees = {}
n = 0
local row = 1
for line in io.lines() do
n = #line
local col = 1
trees[row] = {}
for height in string.gmatch(line, "%d") do
local tree = addTree( row, col, height )
if row&gt;1 and col&gt;1 then
-- ABOVE
local allLess = true
local s = 0
for r=row-1,1,-1 do
s = s+1
if trees[r][col].h &gt;= tree.h then
allLess = false
break
end
end
if allLess then tree.v = true end
tree.s = s*tree.s
-- LEFT
allLess = true
s = 0
for c=col-1,1,-1 do
s = s + 1
if trees[row][c].h &gt;= tree.h then
allLess = false
break
end
end
if allLess then tree.v = true end
tree.s = s*tree.s
end
col = col + 1
end
row = row + 1
end
-- second pass, other direction
for row = n-1, 2, -1 do
for col = n-1, 2, -1 do
local tree = trees[row][col]
-- BELOW
local allLess = true
local s = 0
for r=row+1,n do
s = s + 1
if trees[r][col].h &gt;= tree.h then
allLess = false
break
end
end
if allLess then tree.v = true end
tree.s = tree.s*s
-- RIGHT
allLess = true
s = 0
for c=col+1,n do
s = s + 1
if trees[row][c].h &gt;= tree.h then
allLess = false
break
end
end
if allLess then tree.v = true end
tree.s = tree.s*s
end
end
local sum = 0
local maxscore = 0
for i, row in ipairs(trees) do
for j, tree in ipairs(row) do
-- part 1
if tree.v then sum = sum + 1 end
-- part 2
if tree.s &gt; maxscore and i&gt;1 and i&lt;n and j&gt;1 and j&lt;n then
maxscore = tree.s
end
end
end
print("part 1", sum )
print("part 2", maxscore )
</pre>
</code>
<h2>lessons</h2>
<p>again, i see those loops and part of me wants to abstract them away. &ldquo;a lot of repeated code&rdquo;. it's alright!</p>
<p>i guess there could be more clever ways of going about this; i might think about them later today. maybe it's possible to store the maximum height in each direction, row and column? makes sense, but also i see some problems. at some point, once i had the answers, i thought it could be possible to not use the table for counting visible trees, and just counting them on-the-fly, but i saw that there were some trees being counted twice.</p>
<p>i guess what i did today was alright? at least i'm using the first pass through the data as a way to check for the conditions in two directions already.</p>
<p>today i needed a little bit more than 51 minutes for the first part, and i was done in a total of 61. my rank for the second part is way lower than for the first one; i guess that, by not &ldquo;optimizing&rdquo; (by not knowing strategies for it?) for the first part i left myself a nice structure for adding the second solution.</p>
<p>feeling a little bit sleepy; and it's cold and raining outside. i feel calm, and alright. i'm happy i'm using and learning lua.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="09">
<h1>09 rope bridge</h1>
<p>
<a href="https://adventofcode.com/2022/day/9" rel="external" target="_blank">Day 9: Rope Bridge</a>
</p>
<h2>09 lua</h2>
<p>this was fun!</p>
<p>the following is the code i used to solve part 2 of the puzzle, adapted back to give the answer of part 1 as well.</p>
<code>
<pre>
io.input("input")
function pos2index( pos )
return pos.x*10000 + pos.y
end
function constrain( n, min, max )
-- returns converted n, and if it was converted
local min = min or -1
local max = max or 1
if n &lt; min then return min, true
elseif n &gt; max then return max, true
else return n, false
end
end
local head = { x=1, y=1 }
local n = 9
local tail = {}
for i=1,n do tail[i] = { x=1, y=1 } end
local visitedP1 = { [pos2index(tail[1])] = true }
local resultP1 = 1
local visitedP2 = { [pos2index(tail[n])] = true }
local resultP2 = 1
for line in io.lines() do
local dir, q = string.match(line, "^(%a) (%d+)$")
q = tonumber(q)
for step = 1,q do
if dir == "R" then
head.x = head.x + 1
elseif dir == "L" then
head.x = head.x - 1
elseif dir == "U" then
head.y = head.y + 1
elseif dir == "D" then
head.y = head.y - 1
end
-- loop through tails
for i=1,n do
local h = head
if i&gt;1 then h = tail[i-1] end
local t = tail[i]
local dif = { x=h.x-t.x, y=h.y-t.y }
-- gx, gy indicate if the abs diff was greater than 1
local dx, gx = constrain(dif.x)
local dy, gy = constrain(dif.y)
if dif.x == 0 and gy then -- vertical
t.y = t.y + dy
elseif dif.y == 0 and gx then -- horizontal
t.x = t.x + dx
elseif gx or gy then -- diagonal further away
t.y = t.y + dy
t.x = t.x + dx
end
end
-- part 1
local index = pos2index(tail[1])
if not visitedP1[index] then
visitedP1[index] = true
resultP1 = resultP1 + 1
end
-- part 2
local index = pos2index(tail[n])
if not visitedP2[index] then
visitedP2[index] = true
resultP2 = resultP2 + 1
end
end
end
print("part 1", resultP1)
print("part 2", resultP2)
</pre>
</code>
<p>originally i wasn't accumulating the result during the main loop, and i was assigning the <code>visited</code> list without the conditional. in that version, i counted the final result using <code>pairs</code>, as follows:</p>
<code>
<pre>
local result = 0
for k, v in pairs(visitedP1) do
result = result + 1
end
print("part 1", result)
</pre>
</code>
<h2>lessons</h2>
<p>this was my first time using a function with multiple results! it was nice because it allowed me to simplify my code a little bit; before adding that feature i was using some <code>math.abs</code> to figure out (again) if the absolute difference had been greater than 1.</p>
<p>also, i liked saving the visited list using a distinct index for each coordinate pair. at first i thought i would use strings as keys, but then i decided it would be worth trying using arithmetic. it was nice to know that in principle i would be able to use that system regardless of where the rope was going.</p>
<p>in part 1 i was not using an array for the tail, but i was doing mostly the same thing as what can be seen in the current code. when i saw that in part 2 i had to extrapolate with more tails, i knew it was going to be relatively easy with the system i had in place.</p>
<p>interestingly, this kind of shows in my personal stats: i was done with part 1 in almost 39 minutes (rank 5604), and i was done with everything in 48. my total ranking was the best so far, 3146; before it was 5024 for day 02. i don't want to focus too much on these numbers, but i like them because they might give me some insight on how &ldquo;good&rdquo; or not was my approach.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="10">
<h1>10 cathode-ray tube</h1>
<p>
<a href="https://adventofcode.com/2022/day/10" rel="external" target="_blank">Day 10: Cathode-Ray Tube</a>
</p>
<h2>10 lua</h2>
<p>this was some modulo fun! the following is the integrated and cleaned up version of the programs i used to solve the puzzles and get the stars</p>
<code>
<pre>
io.input("input")
-- for part 2
function pixel( cycle, spx )
local cx = (cycle-1)%40
local s = (cx == 0) and "\n" or ""
local ch = (cx&gt;=spx-1) and (cx&lt;=spx+1) and "#" or "."
return s .. ch
end
local cycle = 1
local x = 1
local strengths = 0 -- part 1
local screen = "" -- part 2
-- process
for line in io.lines() do
local ins, num = string.match(line, "^(%a+) (-?%d+)$")
-- part 1
local period = (cycle-20)%40
if period == 0 then
strengths = strengths + cycle*x
elseif num and period == 39 then
strengths = strengths + (cycle+1)*x
end
-- part 2
screen = screen .. pixel(cycle, x)
if num then -- addx
screen = screen .. pixel(cycle+1, x)
cycle = cycle + 1
x = x + tonumber(num)
end
cycle = cycle + 1
end
print("part 1", strengths)
print("part 2",screen)
</pre>
</code>
<h2>lessons</h2>
<p>the first part was giving me some trouble as i wanted to make sure i covered the case of the relevant cycles being inside an addition instruction. maybe it wasn't needed (at least in my input), but the fact that it was in the example made me cautious about it. it was giving me trouble especially because i felt a little bit slow with my math and starting counts from 1; it's understandable as i was basically waking up!</p>
<p>the second part was super fun and interesting, i wonder how's the creative process behind all these puzzles. i remember last year being delighted about this kind of readable output.</p>
<p>regarding times (?), i finished the first part on 24 minutes and i was done with everything at a little bit more than 39 minutes. this is my second ranked part 2 overall. is it because i'm getting better or because less people are participating, who knows. interestingly, i felt that i took a long time for part 1, and that i was super fast with part 2; the times don't necessarily reflect that.</p>
<p>my result for part 2 surprised me; i thought i had to do more things, including comparing the test input with the given solution, but when i ran it and saw some letters (as i was running it with the given input), i tried them and they were right!</p>
<p>ten days already, wow! feeling good about this process, including giving myself time to write a little bit about each day.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="11">
<h1>11 monkey in the middle</h1>
<p>
<a href="https://adventofcode.com/2022/day/11" rel="external" target="_blank">Day 11: Monkey in the Middle</a>
</p>
<h2>11 part 1 lua</h2>
<p>ouch, this puzzle as a whole bit me hard. part 1 was seemingly straightforward, but then i couldn't extrapolate it (yet?) for the second part. i didn't expect i'd exceed the integer representation limits on day 11!</p>
<p>documenting my partial solution for the moment!</p>
<code>
<pre>
io.input("input")
local monkeys = {}
local lineIndex = 1
local monkey
-- parsing
for line in io.lines() do
if line=="" then
lineIndex = 1
goto continue
end
if lineIndex == 1 then
local newmonkey = { q = {}, targets ={}, count=0 }
monkey = newmonkey
table.insert(monkeys, newmonkey)
elseif lineIndex == 2 then -- starting items
local q = {}
for item in string.gmatch(line, "%d+") do
table.insert( q, tonumber(item) )
end
monkey.q = q
elseif lineIndex == 3 then -- operation
local sign, value = string.match(line, "([+*]) (.+)$")
local num = tonumber(value)
local f
if sign == "*" then
f = function (x) local v = num or x
return x*v
end
else
f = function (x) local v = num or x
return x+v
end
end
monkey.f = f
elseif lineIndex == 4 then -- divisor
monkey.divisor = tonumber(string.match(line, "%d+$"))
elseif lineIndex == 5 then -- targets
monkey.targets[true] = tonumber(string.match(line, "%d+$"))+1
elseif lineIndex == 6 then -- targets
monkey.targets[false] = tonumber(string.match(line, "%d+$"))+1
end
lineIndex = lineIndex + 1
::continue::
end
for round = 1,20 do
for i, monkey in ipairs(monkeys) do
for _, item in ipairs(monkey.q) do
local worry = monkey.f(item)//3
local divisible = worry%monkey.divisor==0
local target = monkey.targets[divisible]
table.insert( monkeys[target].q, worry )
monkey.count = monkey.count + 1
end
monkey.q = {} -- empty queue
end
-- for debugging
--[[
print("round", round)
for i, monkey in ipairs(monkeys) do
print("monkey", i-1, monkey.count)
local oq =""
for _, item in ipairs(monkey.q) do
oq = oq .. tostring(item) .. ","
end
print(oq)
end
]]
end
table.sort(monkeys, function(a,b) return a.count &gt; b.count end)
print("part 1", monkeys[1].count*monkeys[2].count)
</pre>
</code>
<h2>lessons (so far)</h2>
<p>i enjoyed storing the operations that each monkey does as functions/closures! when i saw the problem i remember reading about it and then, looking back at the book, i liked the approach a lot.</p>
<p>additionally, using boolean values as keys in the <code>targets</code> table was very interesting; i didn't expect it to be possible but i liked it for this case.</p>
<p>i also find interesting that i used a label and a <code>goto</code> to emulate a &ldquo;continue&rdquo; present in other languages (skip to the next iteration of the loop).</p>
<p>i managed to use <code>table.sort</code> correctly at the first try, that was nice!</p>
<p>i'll take a break for now.</p>
<h2>part 2</h2>
<p>ok, i asked for some help and i was able to do it!</p>
<p>for a little bit of background, i tried and thought about several ways of simplifying the numbers while attempting to keep the same behavior.</p>
<p>i thought that if all the operations were multiplications that would have been easy, as one could represent the numbers as a series of factors, and that would be it: when multiplying for an already existing factor, i wouldn't add it to the series. for the case of squaring the numbering, i wouldn't add anything. all of this thinking that all the time the checks were divisions, and also noting that the divisors were always prime numbers. actually, i thought of making a list of all the divisors and work something out from there. however, the big problem here is that there were also additions, therefore this approach wouldn't work.</p>
<p>i tried to figure out if there would be a way of doing something similar but including the additions. maybe divide when there were multiplications, and keep everything as is when there were additions? i saw that wouldn't work.</p>
<p>i had given up, i asked for help, and there was the answer.</p>
<p>making a list of divisors was a nice first step, but actually what i needed was to find their least common multiple (i.e. multiplying them all as they were all primes) and use it as the &ldquo;period&rdquo; where meaningful results would happen, applying the modulo operation.</p>
<p>this would work for the addition as it would just wrap around keeping the property of (in)divisibility intact. additionally, 1) all the divisors are not only primes, but actually the first 8 primes, 2) the initial items were either multiples of these primes, or primes themselves. i find it funny that i don't have the algebraic (?) principles to think about modulo, so i had to made several tests to make sure this would work.
</p>
<p>i saw the test answers were right, so there it was!</p>
<p>the following shows how i was getting the divisors (although i didn't use them in the table) and the period, and then how i used it instead of the division over three. everything else stayed the same.</p>
<code>
<pre>
local divisors = {}
local period = 1
for _, monkey in ipairs(monkeys) do
table.insert( divisors, monkey.divisor )
period = period * monkey.divisor
end
for round = 1,10000 do
print("round", round)
for i, monkey in ipairs(monkeys) do
for _, item in ipairs(monkey.q) do
local worry = monkey.f(item)%period
local divisible = worry%monkey.divisor==0
local target = monkey.targets[divisible]
table.insert( monkeys[target].q, worry )
monkey.count = monkey.count + 1
end
monkey.q = {} -- empty queue
end
end
</pre>
</code>
<h2>more lessons</h2>
<p>i take with me that it's okay to let the problem rest, and it's also okay to ask for help! i was almost there, but i probably wouldn't have arrived without the extra push.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="12">
<h1>12 hill climbing algorithm </h1>
<p>
<a href="https://adventofcode.com/2022/day/12" rel="external" target="_blank">Day 12: Hill Climbing Algorithm</a>
</p>
<h2>12 lua</h2>
<p>this took me a while! i'm not good yet with these path traversal algorithms, and arriving at today's solution felt nice. i especially liked how optimal (from my perspective) it ended up being.</p>
<code>
<pre>
io.input("input")
function loc(x, y)
return { x = x, y = y }
end
local map = { v = {} }
-- parsing
local start, dest
local row = 1
local w, h
for line in io.lines() do
if row==1 then w = #line end
local col = 1
map[row] = {}
map.v[row] = {}
for h in string.gmatch(line,"%a") do
local height = string.byte(h)
if h=='S' then
height = string.byte('a')
start = loc( col, row )
elseif h=='E' then
height = string.byte('z')
dest = loc( col, row )
end
map[row][col] = height
map.v[row][col] = false -- visited
col = col + 1
end
row = row + 1
end
map.cols = w
map.rows = row-1
function map:nextMoves( x, y )
local map = self
local cur = map[y][x]
local leftDif = x&gt;1 and not map.v[y][x-1] and cur-map[y][x-1]&lt;=1
local rightDif = x&lt;map.cols and not map.v[y][x+1] and cur-map[y][x+1]&lt;=1
local upDif = y&gt;1 and not map.v[y-1][x] and cur-map[y-1][x]&lt;=1
local downDif = y&lt;map.rows and not map.v[y+1][x] and cur-map[y+1][x]&lt;=1
local moves = {}
if leftDif then table.insert( moves, loc( x-1, y) ) end
if rightDif then table.insert( moves, loc( x+1, y) ) end
if upDif then table.insert( moves, loc( x, y-1) ) end
if downDif then table.insert( moves, loc( x, y+1) ) end
return moves
end
local tocheck = { loc(dest.x, dest.y) }
map.v[dest.y][dest.x] = true -- visited
local count = 1
local count2
repeat
local checking = {}
for _, p in ipairs(tocheck) do
table.insert( checking, p )
end
tocheck = {}
local found = false
for _, p in ipairs(checking) do
map.v[p.y][p.x] = true
local t = map:nextMoves( p.x, p.y )
for _, move in ipairs(t) do
-- insert move only if not there already
local contains = false
for _, m in ipairs(tocheck) do
if m.x==move.x and m.y==move.y then
contains = true
break
end
end
if not contains then table.insert( tocheck, move ) end
-- part 1
if move.x == start.x and move.y == start.y then
found = true
end
-- part2
if not count2 and map[move.y][move.x]==string.byte('a') then
count2 = count
end
end
end
if not found then count = count + 1 end
until found
print("part 1", count)
print("part 2", count2)
</pre>
</code>
<h2>lessons</h2>
<p>first of all, i want to acknowledge that (thankfully) i remembered a solution by <a href="https://merveilles.town/@alderwick" rel="external" target="_blank">@alderwick@merveilles.town</a> from last year, where, as far as i remember, he started from the end and traversed the path backwards. i'm glad i followed that approach!</p>
<p>i had a little bit of trouble figuring out how to proceed: i felt there was going to be recursion and i'm always a little bit scared about it; or then i didn't know the data structure to use: at some point i felt i needed to create some kind of tree to store each possible path, and i was overwhelmed about how to check the visited points in a per-path basis... fortunately i managed to solve it in a relatively simple way!</p>
<p>i liked my approach. to arrive there, i had to iterate it manually to see how i could eventually generalize it and make it compact. for each point to check (starting from the end), i mark it as visited and i check what are the possible moves, and store them into a list of points to check in the next step. as we are looking for the shortest path, it makes sense to mark these points as visited since the first time they appear &mdash; i needed some time to realize and be sure about this, but now it seems obvious. now that i describe this, i feel something like it was the aforementioned approach by alderwick last year.</p>
<p>in my &ldquo;first&rdquo; attempt i was being very inefficient and it was apparent when testing with the real input: i had a table where i was storing all the previous <em>moves</em> (each entry of the table was an array of points) and i wasn't checking for duplicates when adding new ones. this approach almost froze my computer because of the amount of memory being used. that made me realize the duplicates issue, and also that i didn't have to store them; the <em>visited</em> (<code>v</code>) table was enough for the purposes of the problem. (i guess i thought i needed to store of all of them so that i could eventually traverse it back, or something like that)</p>
<p>it was nice to find and improve that aspect, and then see the program working fast to arrive to the answer. overall, this was fun!</p>
<p>as an extra, i tried the notation to make the function a part of the <code>map</code>. it wasn't crucial here, but it served as a little practice.</p>
<p>i find interesting that at the end the code ends up looking way less complicated than what i had envisioned when reading the puzzle. we are still around 100 lines of code! and regarding more stats, i finished part 1 in 99 minutes, and the second in almost 104 &mdash; part 2 was super easy after following the end-to-start approach!</p>
<p>feeling happy and motivated, ready to start the day!</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="13">
<h1>13 distress signal</h1>
<p>
<a href="https://adventofcode.com/2022/day/13" rel="external" target="_blank">Day 13: Distress Signal</a>
</p>
<h2>13 lua</h2>
<p>today was fun! recursion arrived at last and i felt prepared for it. also, i enjoyed using the affordances of the language to parse the data as lists (tables) almost as is &mdash; i had to look it up in the book and i'm glad i'm able to get to learn more and more through this process.</p>
<code>
<pre>
io.input("input")
function compare(left, right)
local t = { l = type(left), r = type(right) }
if t.l == "number" and t.r == "number" then
if left &lt; right then return true
elseif left &gt; right then return false
else return nil
end
elseif t.l ~= t.r then
if t.l == "number" then
return compare( {left}, right )
else
return compare( left, {right} )
end
elseif t.l and t.r then -- two lists
local i = 1
while i &lt;= #left or i &lt;= #right do
local leftE = left[i]
local rightE = right[i]
if not rightE and leftE then
return false
elseif not leftE and rightE then
return true
end
local isCorrect = compare(leftE, rightE)
if isCorrect == nil then
i = i+1
else return isCorrect
end
end
return nil
end
end
local pair = {}
local index = 1
local count = 0
local packets = { }
for line in io.lines() do
if line == "" then
pair = {}
index = index + 1
goto continue
end
-- hehe, convert to lua table and ingest
local t = line:gsub("%[","{"):gsub("%]","}")
local s = "return " .. t
local packet = load(s)()
if not pair.l then
pair.l = packet
else
pair.r = packet
local correct = compare( pair.l, pair.r )
if correct then count = count + index end
end
-- for part 2
table.insert( packets, packet )
::continue::
end
print("part 1", count)
-- part 2
table.insert(packets, {{2}})
table.insert(packets, {{6}})
table.sort( packets, compare )
local d1, d2
for i, p in ipairs(packets) do
local ip = p[1]
if ip and type(ip)=="table" and #ip == 1 and #p == 1 then
if ip[1] == 2 then d1 = i
elseif ip[1] == 6 then d2 = i end
end
end
print("part 2", d1*d2)
</pre>
</code>
<p>for part 2 i wrote a <code>tableToString</code> function to help me out with debugging. recursion again!</p>
<code>
<pre>
function tableToString(t)
local s = "{"
for i, v in ipairs(t) do
if type(v)=="table" then
s = s .. tableToString(v)
else
s = s .. tostring(v)
end
if i&lt;#t then s = s .. "," end
end
s = s .. "}"
return s
end
</pre>
</code>
<p>i needed to see the list of ordered because the input was tricky; or more precisely (?), because at first i was too lax with my conditions to identify the divider packets</p>
<h2>lessons</h2>
<p>important: if you modify the sample file to test some things for part 1, remember you did it for part 2, otherwise you won't be getting the expected result! haha lost some considerable time due to that.</p>
<p>three nice things from lua that i used today and enjoyed:</p>
<ol>
<li>the <code>load</code> function that parses a string as code (note for the future: related functions are <code>loadfile</code> and <code>dofile</code>). i didn't feel in the mood of manually parsing the lists and this helped me with that. additionally, i hadn't used this tool before so i found it was worth applying the concept, as i had read about it at some point.</li>
<li>the <code>table.sort</code> function that uses an <em>order</em> function; i had used it before with simpler comparisons and today i was glad (and surprised!) i could just &ldquo;plug in&rdquo; the function i had written for part 1.</li>
<li>the <code>type</code> function; it's the first time i use it in my final program and it was crucial to differentiate between lists and numbers.</li>
</ol>
<p>given that i'm still waking up early to solve these, i have the times i needed for each part. i'll keep sharing them: i finished part 1 in a little bit more than 47 minutes, and i was done in almost 76. after day 1, this is the best ranked part 1 &mdash; i guess my parsing approach did help a lot!</p>
<p>i remember last year there was a puzzle like this one but i'm not sure how i parsed and managed the lists in <code>awk</code> back then. it could be worth looking into that and remember; however today i'm glad i used the power of lua. the power of small?</p>
<p>i'm feeling very well equipped with lua. i feel that this year i'll able to finish all the puzzles. at least today i felt comfortable with recursion. let's see what comes next!</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="14">
<h1>14 regolith reservoir</h1>
<p>
<a href="https://adventofcode.com/2022/day/14" rel="external" target="_blank">Day 14: Regolith Reservoir</a>
</p>
<h2>14 lua</h2>
<p>today was intense and fun!</p>
<code>
<pre>
io.input("input")
function string2point( s )
local x, y = string.match( s, "(%d+),(%d+)" )
return { x=tonumber(x), y=tonumber(y) }
end
function coord2string( x, y )
return tostring(x) .. "," .. tostring(y)
end
map = {}
map.min = { x=1000, y=1000 }
map.max = { x=1, y=1 }
function map:updateRange( p )
local map = self
if p.x&lt;map.min.x then map.min.x = p.x
elseif p.x&gt;map.max.x then map.max.x = p.x
end
if p.y&lt;map.min.y then map.min.y = p.y
elseif p.y&gt;map.max.y then map.max.y = p.y
end
end
function map:fillWithRock( s1, s2 )
local map = self
local p1 = string2point( s1 )
local p2 = string2point( s2 )
map:updateRange( p1 )
map:updateRange( p2 )
if p1.x == p2.x then
local x = p1.x
local inc = (p1.y &gt; p2.y) and -1 or 1
for y = p1.y, p2.y, inc do
local s = coord2string( x, y )
map[s] = "#"
end
elseif p1.y == p2.y then
local y = p1.y
local inc = (p1.x &gt; p2.x) and -1 or 1
for x = p1.x, p2.x, inc do
local s = coord2string( x, y )
map[s] = "#"
end
end
end
function map:blocked( x, y )
local map = self
local s = coord2string( x, y )
return map[s] or y==map.max.y+2
end
function map:printArea( x1, y1, x2, y2 )
local map = self
print( map.min.x, map.min.y, map.max.x, map.max.y)
for row = y1, y2 do
local rs = ""
for col = x1, x2 do
local cs = coord2string( col, row )
local symbol = map[cs] or "."
rs = rs .. symbol
end
print( row, rs )
end
end
-- parse
for line in io.lines() do
local s1, s2
local i = 1
for point in string.gmatch( line, "(%d+,%d+)" ) do
s2 = point
if s1 then
map:fillWithRock( s1, s2 )
end
s1 = s2
i = i + 1
end
end
local result1, result2
local done = false
local i = 1
while not done do
local unit = { x=500, y=0 }
repeat
local moving = true
local nexty = unit.y+1
-- part 1
if nexty &gt; map.max.y and not result1 then
result1 = i - 1
--done=true
end
if not map:blocked( unit.x, nexty ) then
unit.y = unit.y+1
elseif not map:blocked( unit.x-1, nexty ) then
unit.x = unit.x-1
unit.y = unit.y+1
elseif not map:blocked( unit.x+1, nexty) then
unit.x = unit.x+1
unit.y = unit.y+1
else
local s = coord2string( unit.x, unit.y )
map[s] = "o"
moving = false
-- part 2
if unit.y == 0 then
result2 = i
done = true
end
end
until not moving or done
i = i + 1
end
map:printArea( 490, 0, 503, 11 )
print("part 1", result1)
print("part 2", result2)
</pre>
</code>
<h2>lessons</h2>
<p>don't forget to convert <code>tonumber</code>! when i decided i was going to use strings as keys for the maps, i was having some problems because at some point i was looping using automatically converted strings to numbers (and therefore, those were floats), and the resulting keys were different than the ones i had used. some keys had a decimal point, and others didn't. i had to look up the issue in the book, and found the following, regarding lua 5.3 formatting floats and integeres in a different way.
<quote><em>However, more often than not, this problem indicates a deeper flaw somewhere else, where an integer becomes a float with no good reason</em></quote>. i eventually realized it was because of the lack of the explicit <code>tonumber</code> call.
</p>
<p>at first i thought of using a bidimensional list (a <em>table</em>?) for the map because using strings as keys didn't seem as elegant (?), but then it was giving me more problems than not (e.g. what should be it's original size? should i create new rows as they are needed?) so i decided to go with the &ldquo;simple&rdquo; way. i made my helper functions to convert back and forth from coordinate pairs to strings, and it worked alright! (except for the aforementioned problem!)</p>
<p>today i spent almost 74 minutes in the first part, and i was done in a little bit more than 86. i did take a couple of breaks, especially after the successful parsing of the map, but i felt time flew! i didn't feel overwhelmed or <em>blocked</em>, and also i didn't find it tedious. it just was that it was a lot to do, everything with its own interesting set of challenges.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="15">
<h1>15 </h1>
<p>
<a href="https://adventofcode.com/2022/day/15" rel="external" target="_blank">Day 15: Beacon Exclusion Zone</a>
</p>
<h2>15 lua</h2>
<p>this was intense! a lot of optimizations went happened, from naively using arrays (as can be seen still in part 1, but it was &rdquo;worse&rquo;) to using ranges and join operations in part 2. i tried further optimizing the latter, but i wasn't getting the right answer. so for the moment i'm leaving it as is:</p>
<code>
<pre>
io.input("input")
local targetRow = 2000000
local maxx, maxy = 4000000, 4000000
function abs(x)
return (x&gt;=0) and x or -x
end
function distance(x1, y1, x2, y2)
return abs(x1-x2) + abs(y1-y2)
end
function inRange( x, y, row, targetD )
local startD = abs(row-y)
local dif = targetD - startD
if dif&lt;0 then return nil
else return x-dif, x+dif
end
end
local count = 0
local positions = {}
local beacons = {} -- beacons in row
local data = {}
for line in io.lines() do
local nums = {}
for match in string.gmatch(line, "(-?%d+)") do
table.insert( nums, tonumber(match) )
end
local sx, sy, bx, by = table.unpack(nums)
local d = distance(sx, sy, bx, by)
local r1, r2 = inRange(sx, sy, targetRow, d)
-- print(sx, sy, bx, by, d)
if r1 then
for c = r1,r2 do
positions[c] = true
end
end
if by == targetRow then
beacons[bx] = true
end
-- for part 2
table.insert(data, {sx, sy, bx, by, d})
end
local result1 = 0
local min, max = math.maxinteger, math.mininteger
for x,v in pairs(positions) do
if not beacons[x] then
result1 = result1 + 1
end
end
print("part 1", result1)
-- part 2
function join( r1, r2 )
local mini = (r1.i &lt; r2.i ) and r1.i or r2.i
local maxf = (r1.f &gt; r2.f ) and r1.f or r2.f
if r2.i &lt;= r1.f+1 and r1.i&lt;=r2.i or r1.i &lt;= r2.f+1 and r2.i &lt;= r1.i then
return { i = mini, f = maxf }
else
return r1, r2
end
end
local minx, miny = 0, 0
local beaconX, beaconY
for y = miny, maxy do
if y%500000==0 then print("#"..tostring(y)) end
local ranges = {}
local beacons = {} -- beacons in row
for _, t in ipairs(data) do
local sx, sy, bx, by, d = table.unpack(t)
local ri, rf = inRange(sx, sy, y, d)
if ri then
ri = (ri &gt; minx) and ri or minx
rf = (rf &lt; maxx) and rf or maxx
local range = { i=ri, f=rf }
if #ranges == 0 then
table.insert( ranges, range )
else
local i = 1
repeat
local done
local r1, r2 = join( ranges[i], range )
if not r2 then
ranges[i] = r1 -- joined range
done = true
else -- don't intersect, try again
i = i + 1
-- unless there are no more
if i &gt; #ranges then
ranges[i] = range
done = true
end
end
until done
end
end
end
if #ranges &gt; 1 then
-- clean up
local i = 2
repeat
local r1, r2 = join( ranges[i], ranges[i-1] )
if not r2 then
ranges[i-1] = r1 -- joined range
table.remove( ranges, i )
else -- don't intersect, try again
i = i + 1
end
until i &gt; #ranges
end
-- if there are still two ranges, here's the answer
if #ranges &gt; 1 then
local r1, r2 = ranges[1], ranges[2]
local minf = (r1.f&gt;r2.f) and r2.f or r1.f
beaconX = minf+1
beaconY = y
end
if beaconX then break end
end
local result2 = beaconX*4000000 + beaconY
print("part 2", result2)
</pre>
</code>
<h2>lessons</h2>
<p>this was a nice problem for seeing how a &ldquo;naive&rdquo; initial approach wouldn't work for other conditions.</p>
<p>at some point i wanted to implement an iterator, but i decided to try it first with a list and a normal <code>for</code> loop. i'm glad i didn't implement it because that approach was one of the things that changed a lot; however i'm still curious about learning about using those.</p>
<p>in part 1 i spent almost 50 minutes at it was the second best ranked part 1, so far. i was done in 2 hours and 50 minutes; wow! 2 hours in the second part!</p>
<p>i'm still curious about part 2; i'm not super happy about how the &rdquo;check and add a new range&ldquo; part looks, but whenever i tried to change it to something like the <em>clean up</em> below, i wasn't getting the answer. it might be worth looking whenever i have more available time (!)</p>
<p>feeling happy and motivated to keep going!</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="16">
<h1>16 proboscidea volcanium</h1>
<p>
<a href="https://adventofcode.com/2022/day/16" rel="external" target="_blank">Day 16: Proboscidea Volcanium</a>
</p>
<p>not finished yet</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="17">
<h1>17 pyroclastic flow</h1>
<p>
<a href="https://adventofcode.com/2022/day/17" rel="external" target="_blank">Day 17: Pyroclastic Flow</a>
</p>
<h2>17 lua</h2>
<p>wow, this was intense! glad i could finish</p>
<p>this is my somewhat cleaned up code! because of what i did to solve part 2, now one has to change the value of the <code>target</code> variable to choose with part to solve.</p>
<code>
<pre>
io.input("input")
--
--local target = 2022 -- part 1
local target = 1000000000000 -- part 2
local shapes = {
{ w = 4, h = 1, map = { {1, 1, 1, 1} } },
{ w = 3, h = 3, map = { {0, 1, 0}, {1, 1, 1}, {0, 1, 0} } },
{ w = 3, h = 3, map = { {1, 1, 1}, {0, 0, 1}, {0, 0, 1} } },
{ w = 1, h = 4, map = { {1}, {1}, {1}, {1} } },
{ w = 2, h = 2, map = { {1, 1}, {1, 1} } },
}
local f = io.read("a")
local jets = {}
for j in string.gmatch(f,"[&lt;&gt;]") do
table.insert( jets, j)
end
function emptiness()
return {0, 0, 0, 0, 0, 0, 0}
end
local tunnel = {}
function tunnel:print()
for i = #self, 1, -1 do
local s = ""
local row = self[i]
for _, c in ipairs(row) do
s = s .. (c == 1 and "@" or ( c==2 and "#" or "."))
end
print(s)
end
end
function tunnel:drawShape( shape, shX, shY, color)
local tunnel = self
local color = color or 1
for ly = 1, shape.h do
local y = ly + shY - 1
tunnel[y] = tunnel[y] or emptiness()
for lx = 1, shape.w do
local x = lx + shX - 1
tunnel[y][x] = shape.map[ly][lx]==1 and color or tunnel[y][x]
end
end
end
function tunnel:clearShape( shape, shX, shY )
self:drawShape( shape, shX, shY, 0 )
end
function equals( t1, t2 )
for i = 1, #t1 do
if t1[i] ~= t2[i] then return false end
end
return true
end
local shI = 1
local jeI = 1
local erasedRows = 0
local iFound, iPeriod
local i = 1
while i &lt;= target do
local shape = shapes[shI]
local x = 3 -- left
local y = #tunnel + 4 -- bottom
for i = 1, 3 do
table.insert( tunnel, emptiness() )
end
repeat -- moving loop
tunnel:drawShape( shape, x, y )
-- jet
local jet = jets[jeI]
local inc = jet=="&lt;" and -1 or 1
local canMove = false
if (inc == -1 and x &gt; 1 ) or ( inc == 1 and x + shape.w - 1 &lt; 7) then
canMove = true
for lx = 1, shape.w do
local shX = lx + x - 1
for ly = 1, shape.h do
local shY = ly + y - 1
if tunnel[shY][shX+inc] == 2 and shape.map[ly][lx]==1 then
canMove = false
end
end
end
end
if canMove then
tunnel:clearShape( shape, x, y )
x = x + inc
tunnel:drawShape( shape, x, y )
end
jeI = (jeI == #jets) and 1 or (jeI + 1)
-- down
canMove = (y &gt; 1)
if canMove then
for lx = 1, shape.w do
local shX = lx + x - 1
for ly = 1, shape.h do
local shY = ly + y - 1
if tunnel[shY-1][shX] == 2 and shape.map[ly][lx]==1 then
canMove = false
break
end
end
if not canMove then break end
end
end
if canMove then
tunnel:clearShape( shape, x, y )
y = y - 1
tunnel:drawShape( shape, x, y )
end
until not canMove
-- stopped shape
tunnel:drawShape( shape, x, y, 2)
-- clean tunnel
repeat
local emptyRow = true
local ind = #tunnel
for _, c in ipairs( tunnel[ind] ) do
if c &gt; 0 then
emptyRow = false
break
end
end
if emptyRow then
table.remove( tunnel )
end
until not emptyRow or #tunnel == 0
-- check for repetition
local period
for size = 10, #tunnel/2 do
local equal = true
for j = 1, size do
local tail = tunnel[ #tunnel - j + 1 ]
local tail2 = tunnel[ #tunnel - size - j + 1 ]
if not equals(tail, tail2) then
equal = false
break
end
end
if equal then
period = size
for i = 1,period do
table.remove(tunnel)
end
erasedRows = erasedRows + period
if not iFound then
iFound = i
elseif not iPeriod then
iPeriod = i - iFound
-- jump ahead!
local remaining = target - i
local advance = remaining // iPeriod
local outOfIPeriod = remaining % iPeriod
erasedRows = erasedRows + advance*period
i = target - outOfIPeriod
end
-- print(i, iFound, period, iPeriod, #tunnel )
break
end
end
shI = (shI == #shapes) and 1 or (shI + 1)
i = i + 1
end
print("result ", #tunnel + erasedRows)
</pre>
</code>
<h2>lessons</h2>
<p>i'm guessing the input was constructed in such a way that there would be repetition in the final sequence; i don't feel i have the mathematical tools to figure that out. i'm still not sure why it happens and why the period is less than the size of the input (oh, now that i write it, i guess there is repetition in the input as well?)</p>
<p>i'm happy i could figure out the math to arrive at the solution once the period was found, especially because it wasn't straightforwad because the amount of shapes doesn't correspond to the amount of growth.</p>
<p>for the first part i needed 2 hours, and i as done in 3:34 hours. funnily enough, this is my best ranked puzzle by far, with a ranking of 2161.</p>
<p>time to go back to day 16?</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="18">
<h1>18 boiling boulders</h1>
<p>
<a href="https://adventofcode.com/2022/day/18" rel="external" target="_blank">Day 18: Boiling Boulders</a>
</p>
<h2>18 part 1, lua</h2>
<p>i took a little bit longer than expected because i was mixing some processes, but i had fun arriving at this solution!</p>
<code>
<pre>
io.input("input")
function s2p( s )
local nx, ny, nz = string.match(s, "(%d+),(%d+),(%d+)")
return tonumber(nx), tonumber(ny), tonumber(nz)
end
function p2s( x, y, z )
return string.format("%d,%d,%d", x, y, z )
end
local cubes = {}
local potneigh = {}
local ncubes = 0
for line in io.lines() do
local s = line
local x, y, z = s2p(s)
ncubes = ncubes + 1
cubes[ s ] = true
local dirs = {}
table.insert( dirs, p2s( x, y, z+1) )
table.insert( dirs, p2s( x, y, z-1) )
table.insert( dirs, p2s( x, y+1, z) )
table.insert( dirs, p2s( x, y-1, z) )
table.insert( dirs, p2s( x+1, y, z) )
table.insert( dirs, p2s( x-1, y, z) )
for _, d in ipairs(dirs) do
potneigh[d] = (potneigh[d] or 0) + 1
end
end
local neigh = 0
for d, sum in pairs(potneigh) do
if cubes[d] then
neigh = neigh + sum
end
end
local result1 = ncubes*6 - neigh1
print("part 1", result1)
</pre>
</code>
<p>for part 2 my initial approach of counting the empty cubes that are neighbors to 6 cubes was wrong. there are probably larger volumes of empty pockets, and for the moment i'm not sure how to find and count them.</p>
<h2>lessons</h2>
<p>i'm getting used to the &ldquo;lua-isms&rdquo; for conditionals in the same line; however sometimes it might be worth to first use a more verbose <code>if</code> to make sure i'm not leaving anything behind.</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="20">
<h1>20</h1>
<p>
<a href="https://adventofcode.com/2022/day/20" rel="external" target="_blank">Day 20</a>
</p>
<h2>20 lua</h2>
<p>thank you <a href="https://merveiles.town/@alderwick" rel="external" target="_blank">@alderwick@merveilles.town</a> for your help and hints for solving this puzzle!</p>
<code>
<pre>
io.input("input")
local key = 1 -- part 1
local key = 811589153 -- part 2
local mixed = {}
local i = 0
for line in io.lines() do
local num = tonumber(line)*key
table.insert( mixed, { num = num, pos=i} )
i = i + 1
end
local n = #mixed
function wrap(x)
return x%n
end
local pos0 = {}
local it = 1
local nit = 10
local sums = {}
for it = 1,nit do
for i, d in ipairs(mixed) do
if d.num == 0 then goto nextnum end
local ppos = d.num+d.pos
local inc = 0
if ppos &gt;= n or ppos &lt;0 then
inc = ppos//(n-1)
end
local newpos = wrap( ppos + inc )
for j, t in ipairs(mixed) do
if j==i then goto continue end
if newpos &lt; d.pos and t.pos&gt;=newpos and t.pos&lt;d.pos then
t.pos = t.pos + 1
elseif newpos &gt; d.pos and t.pos&lt;=newpos and t.pos&gt;d.pos then
t.pos = t.pos - 1
end
::continue::
end
d.pos = newpos
::nextnum::
end
-- find position of 0
for i, d in ipairs(mixed) do
if d.num == 0 then pos0[it] = d.pos break end
end
print(it, "pos0", pos0[it])
sums[it] = 0
end
for j = 1, nit do
for i, d in ipairs(mixed) do
if d.pos == wrap(pos0[j] + 1000)
or d.pos == wrap(pos0[j]+2000)
or d.pos == wrap(pos0[j]+3000) then
sums[j] = sums[j] + d.num
end
end
end
print("part 1", sums[1])
print("part 2", sums[10])
</pre>
</code>
<p><a href="#top">back to top</a></p>
</section>
<section id="21">
<h1>21 monkey math</h1>
<p>
<a href="https://adventofcode.com/2022/day/21" rel="external" target="_blank">Day 21: Monkey Math</a>
</p>
<h2>21 lua</h2>
<p>back on track! (?)</p>
<code>
<pre>
io.input("input")
local m = {}
for line in io.lines() do
local pattern = "^(%a+): (.+)$"
local name, arg = string.match(line, pattern)
local num = tonumber(arg)
if num then
m[name] = function() return num end
else
local pattern = "(%a+) ([%+%-%*%/]) (%a+)"
local a, op, b = string.match(arg, pattern)
if op == "+" then
m[name] = function() return m[a]()+m[b]() end
-- for part 2
if name == "root" then
m["root2"] = function() return m[a]()-m[b]() end
end
elseif op == "-" then
m[name] = function() return m[a]()-m[b]() end
elseif op == "*" then
m[name] = function() return m[a]()*m[b]() end
elseif op == "/" then
m[name] = function() return m[a]()/m[b]() end
end
end
end
local result1 = m["root"]()
print( string.format("part 1: %d", result1) )
-- part 2
local at = 0
local inc = 10000000000
repeat
repeat
m["humn"] = function() return at end
dif = m["root2"]()
at = at + inc
until dif &lt;= 0
if dif==0 then break end
at = at - 10*inc
inc = inc/10
until inc &lt; 1
local result2 = at - inc
print( string.format("part 2: %d", result2))
</pre>
</code>
<h2>lessons</h2>
<p>i liked i could apply what i learned from the <a href="#11">previous monkey math</a> puzzle, plus what i learned from some other tests i did at the moment to make sure i could define a function calling functions that are not defined yet (i could! if that wasn't the case i had a planned workaround). this made the first part surprisingly straightforward!</p>
<p>for the second part i found the result iterating &ldquo;manually&rdquo;. once i had it right i modified my code so that the program would replicate the process i was following and would find the answer by itself. interestingly, this is the first time i don't make sure i can find the answer with the example input; actually the program as is doesn't work for it (?)</p>
<p>it feels strange to somewhat brute-force it, but that search seemed more attainable to me than solving it in a more &ldquo;elegant&rdquo; way. to review later, symbolic math handling!</p>
<p>this is my best ranked puzzle by far; i finished the first part in a little bit more than 14 minutes and i was done (with the manually-obtained answer) in 42, ranked in 1091 (!).</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="23">
<h1>23 unstable diffusion</h1>
<p>
<a href="https://adventofcode.com/2022/day/23" rel="external" target="_blank">Day 23: Unstable Diffusion</a>
</p>
<h2>23 lua</h2>
<p>this was fun!</p>
<code>
<pre>
io.input("input")
function p2s( x, y )
return string.format("%d,%d", x, y)
end
function s2p( s )
local x, y = string.match(s,"(-?%d+),(-?%d+)")
x = tonumber(x)
y = tonumber(y)
return x, y
end
function neighbors(x, y)
local n = {}
n.NW = p2s( x-1, y-1)
n.N = p2s( x, y-1)
n.NE = p2s( x+1, y-1)
n.E = p2s( x+1, y )
n.SE = p2s(x+1, y+1)
n.S = p2s(x, y+1)
n.SW = p2s(x-1, y+1)
n.W = p2s(x-1, y)
return n
end
local max = {y=0, x=0}
local min = {y=10000, x=10000}
function updateMaxMin( j, i )
if j&gt;max.x then max.x = j
elseif j&lt;min.x then min.x = j end
if i&gt;max.y then max.y = i
elseif i&lt;min.y then min.y = i end
end
local nelves = 0
local elves = {}
-- parse map
local i = 1
for line in io.lines() do
local j = 1
for c in string.gmatch(line, "%g") do
if c=="#" then
elves[ p2s(j,i) ] = true
updateMaxMin(j, i)
nelves = nelves + 1
end
j = j + 1
end
i = i + 1
end
local dirs = {"N", "S", "W", "E"}
local offdirs = 0
local round = 1
local result1
repeat
-- first half
local attempts = {}
for s, e in pairs(elves) do
local x, y = s2p(s)
local alone = true
local n = neighbors(x,y)
for k, ns in pairs(n) do
if elves[ns] then alone = false break end
end
if alone then goto continue end
for i=0,3 do
local idirs = (i+offdirs)%4 + 1
local dir = dirs[idirs]
if dir=="N" and not (elves[n.NW] or elves[n.N] or elves[n.NE]) then
--print("propose north")
if attempts[n.N] == nil then
attempts[n.N] = s
elseif attempts[n.N] then -- attempt
attempts[n.N] = false
end
break
elseif dir=="S" and not (elves[n.SW] or elves[n.S] or elves[n.SE]) then
--print("propose south")
if attempts[n.S] == nil then
attempts[n.S] = s
elseif attempts[n.S] then -- attempt
attempts[n.S] = false
end
break
elseif dir=="W" and not (elves[n.SW] or elves[n.W] or elves[n.NW]) then
--print("propose west")
if attempts[n.W] == nil then
attempts[n.W] = s
elseif attempts[n.W] then -- attempt
attempts[n.W] = false
end
break
elseif dir=="E" and not (elves[n.SE] or elves[n.E] or elves[n.NE]) then
-- print("propose east")
if attempts[n.E] == nil then
attempts[n.E] = s
elseif attempts[n.E] then -- attempt
attempts[n.E] = false
end
break
end
end
::continue::
end
--second half
local count = 0
for k, s in pairs(attempts) do
if s then
elves[s] = nil
elves[k] = true
local nx, ny = s2p(k)
updateMaxMin(nx, ny)
count = count + 1
end
end
--[[
print("moved:",count)
print(min.x, min.y, max.x, max.y)
--]]
offdirs = (offdirs==3) and 0 or (offdirs+1)
if round%100==0 then
print("round", round)
end
if round == 10 then
result1 = (max.y-min.y+1)*(max.x-min.x+1)-nelves
end
if count &gt; 0 then
round = round + 1
end
until count == 0
print("part 1", result1)
print("part 2", round)
-- print map:
for y=min.y,max.y do
local m = ""
for x=min.x,max.x do
local s = p2s(x,y)
m = m .. (elves[s] and "#" or ".")
end
-- print(m)
end
</pre>
</code>
<h2>lessons</h2>
<p>for converting the positions to keys i was using some arithmetic, but that failed me when the horizontal coordinates were negative. i went back to the approach of using strings as keys, but i guess there would be other ways of solving it; the back-and-forth from strings to numbers is probably not very efficient.</p>
<p>probably there's a way to simplify the way i check and create the attempted moves. in any case i enjoyed the approach: set the attempted cell to false when someone else had attempted it already.</p>
<p>for this puzzle i spent 102 minutes in part 1, and i was done in 110 (! interesting number from the puzzle)</p>
<p>let's see what comes next in the final days!</p>
<p><a href="#top">back to top</a></p>
</section>
<section id="25">
<h1>25 full of hot air</h1>
<p>
<a href="https://adventofcode.com/2022/day/25" rel="external" target="_blank">Day 25: Full of Hot Air</a>
</p>
<h2>23 part 1, lua</h2>
<p>this was fun!</p>
<code>
<pre>
io.input("input")
function s2d( s )
local b = #s - 1
local num = 0
for i = 1, #s do
local sub = string.sub( s, i, i )
local d = tonumber(sub)
if not d then
if sub=="-" then d=-1
elseif sub=="=" then d=-2 end
end
num = num + d*(5^b)
b = b - 1
end
return num
end
function d2s( num )
local s = ""
repeat
local mod = num % 5
local rest = num // 5
local dif = mod - 5
local digit
if dif &gt;= -2 then
rest = rest + 1
if dif == -2 then digit = "="
elseif dif == -1 then digit = "-" end
else
digit = string.format("%d", mod)
end
s = digit .. s
num = rest
until num == 0
return s
end
local sum = 0
for line in io.lines() do
sum = sum + s2d(line)
end
local result1 = d2s(sum)
print("part1:", result1)
</pre>
</code>
<p>i couldn't continue with part 2 because it required me to have all the previous puzzles done, oh well!</p>
<h2>lessons</h2>
<p>i'm glad i woke up for this one! nice and easy way to end this enjoyable and fun process.</p>
<p>i really like lua now!</p>
<p><a href="#top">back to top</a></p>
</section>
</main>
<footer>
<p>página creada en <time datetime="2022-12-01">120221201</time></p>
<p>actualizada en <time datetime="2022-12-25">120225225</time></p>
</footer>
</body>
</html>