Day 3: Mull It Over
Today we’re parsing. I’ll solve this by writing a stateful parser.
This will need a new parser repeated
that is like
many
but not keeping any results.
export repeated
function repeated(p::P) where {P <: Parser}
function (s::S) where {S <: AbstractString}
while true
try
= parse(p, s)
(x, s) catch
return (nothing, s)
end
end
end |> FnParser
end
Part 1
In Part 1, we create a closure multiply
for multiplying
numbers and adding them to the total, as well as a result
closure that obtains the total.
function part1_parser()
= 0
total
multiply(a, b) = begin
+= a * b
total pure_p(nothing)
end
result(_) = begin
pure_p(total)
end
= sequence(
mul_p match_p("mul(") >>> integer_p,
match_p(",") >>> integer_p >> skip(match_p(")"))
>> splat(multiply)
) = item_p >>> pure_p(nothing)
noise_p
repeated(mul_p | noise_p) >> result
end
Part 2
Now we need to add a second bit of state to the closure, telling us whether the multiplication machine is enabled or not.
function part2_parser()
= true
enabled = 0
total
enable(_) = begin enabled = true; pure_p(nothing) end
disable(_) = begin enabled = false; pure_p(nothing) end
multiply(a, b) = begin
+= (enabled ? a * b : 0)
total pure_p(nothing)
end
result(_) = begin pure_p(total) end
= match_p("do()") >> enable
do_p = match_p("don't()") >> disable
dont_p = sequence(
mul_p match_p("mul(") >>> integer_p,
match_p(",") >>> integer_p >> skip(match_p(")"))
>> splat(multiply)
) = item_p >>> pure_p(nothing)
noise_p
repeated(mul_p | do_p | dont_p | noise_p) >> result
end
That wraps it up for today.
module Day03
using ..Monads
using ..Parsing
<<day03>>
function main(io::IO)
= read(io, String)
text = text |> parse(part1_parser()) |> result
part1 = text |> parse(part2_parser()) |> result
part2 return (part1, part2)
end
end
# add tests
Python
I like functional programming. People often ask me then “what does that mean?”. Well, truly, no one really knows.
- Have a distrust for mutable state
- Prefer pure functions over classes
- Use composition to build out complexity
When it comes to Advent of Code, I tend to prefer building my own parsers over using Regexes. Let’s solve Day 3, Part 2 in Python. I will break all these preferences, but everything will still feel functional somehow.
from __future__ import annotations
from dataclasses import dataclass
from collections.abc import Callable
<<day03-python>>
@dataclass
class Parser[T]:
str], tuple[T, str]]
f: Callable[[
def __rshift__(self, f: Callable[[T], Parser[U]]) -> Parser[U]:
return bind(self, f)
def parse[T](p: Parser[T], s: str) -> T:
return p.f(s)[0]
def parser[T](f: Callable[[str], tuple[T, str]]) -> Parser[T]:
return Parser(f)
def bind[T, U](a: Parser[T], f: Callable[[T], Parser[U]]) -> Parser[U]:
@parser
def bound(s: str) -> tuple[U, str]:
= a.f(s)
(x, s) return f(x)(s)
return bound
class Failure(Exception):
pass
class EndOfInput(Failure):
def __str__(self):
return "End of input"
@parser
def item(s: str) -> tuple[str, str]:
if len(s) == 0:
raise EndOfInput()
return (s[0], s[1:])
def many[T](p: Parser[T]) -> Parser[list[T]]:
@parser
def many_p(s: str) -> tuple[list[T], str]:
= []
result while True:
try:
= p.f(s)
(x, s) except Failure:
return (result, s)
result.append(x)return many_p
def test_many_items():
assert parse(many(item), "abc") == ["a", "b", "c"]
def sequence(*ps: Parser[Any]) -> Parser[Any]:
def sequenced(s: str) -> tuple[Any, str]:
for