2016 / 2017 / 2018 / 2019 / 2020 / 2021
- Day 1
- Day 2
- Day 3 (no reflection yet)
- Day 4 (no reflection yet)
- Day 5 (no reflection yet)
- Day 6 (no reflection yet)
- Day 7 (no reflection yet)
- Day 8 (no reflection yet)
- Day 9 (no reflection yet)
- Day 10 (no reflection yet)
- Day 11 (no reflection yet)
- Day 15 (no reflection yet)
- Day 16 (no reflection yet)
Prompt / Code / Rendered / Standalone Reflection Page
As a simple data processing thing, this one shines pretty well in Haskell :)
Assuming we have a list, we can get the consecutive items with a combination of
zipWith
and drop
. Then we can just count how many pairs of items match the
predicate (strictly increasing):
countIncreasesPart1 :: [Int] -> Int
countIncreasesPart1 xs = length (filter (== True) (zipWith (<) xs (drop 1 xs)))
Yes, filter (== True)
is the same as filter id
, but it's a bit easier to
read this way :)
Remember that if xs
is [2,4,6,5]
, then drop 1 xs
is [4,6,5]
, and so
zip xs (drop 1 xs)
is [(2,4), (4,6), (6,5)]
So zipWith (<) xs (drop 1 xs)
is [True, True, False]
. So counting all of the True
items yields the
right answer!
Part 2 is very similar, but we need to check if items three positions apart
are increasing. That's because for each window, the sum of the window is
increasing if the new item gained is bigger than the item that was just lost.
So for an example like [3,5,6,4,7,8]
, as we move from [3,5,6]
to [5,6,4]
,
we only need to check if 4
is greater than 3
. So we only need to compare 4
and 3, 7 and 5, and then 8 and 6.
countIncreasesPart2 :: [Int] -> Int
countIncreasesPart2 xs = length (filter (== True) (zipWith (<) xs (drop 3 xs)))
We just need to replace drop 1 xs
with drop 3 xs
to compare three-away
items.
Anyway the parsing in Haskell is straightforward, at least -- we can just do
map read . lines
, to split our input into lines and then map read :: String -> Int
over each line. Ta dah! Fun start to the year :)
>> Day 01a
benchmarking...
time 26.81 μs (26.45 μs .. 27.29 μs)
0.996 R² (0.991 R² .. 1.000 R²)
mean 26.75 μs (26.43 μs .. 27.57 μs)
std dev 1.478 μs (130.1 ns .. 2.625 μs)
variance introduced by outliers: 62% (severely inflated)
* parsing and formatting times excluded
>> Day 01b
benchmarking...
time 27.02 μs (25.16 μs .. 28.74 μs)
0.966 R² (0.956 R² .. 0.979 R²)
mean 26.40 μs (25.02 μs .. 27.78 μs)
std dev 4.752 μs (3.640 μs .. 6.699 μs)
variance introduced by outliers: 95% (severely inflated)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Day 2 has a satisfying "unified" solution for both parts that can be derived from group theory! The general group (or monoid) design pattern that I've gone over in many Advent of Code blog posts is that we can think of our "final action" as simply a "squishing" of individual smaller actions. The revelation is that our individual smaller actions are "combinable" to yield something of the same type, so solving the puzzle is generating all of the smaller actions repeatedly combining them to yield the final action.
In both of these parts, we can think of squishing a bunch of small actions
(forward
, up
, down
) into a mega-action, which represents the final trip
as one big step. So here is our general solver:
-- | A type for x-y coordinates/2d vectors
data Point = P { pX :: Int, pY :: Int }
day02
:: Monoid r
=> (Int -> r) -- ^ construct a forward action
-> (Int -> r) -- ^ construct an up/down action
-> (r -> Point -> Point) -- ^ how to apply an action to a point
-> String
-> Point -- ^ the final point
day02 mkForward mkUpDown applyAct =
(`applyAct` P 0 0) -- get the answer by applying from 0,0
. foldMap (parseAsDir . words) -- convert each line into the action and merge
. lines -- split up lines
where
parseAsDir (dir:n:_) = case dir of
"forward" -> mkForward amnt
"down" -> mkUpDown amnt
"up" -> mkUpDown (-amnt)
where
amnt = read n
And there we have it! A solver for both parts 1 and 2. Now we just need to pick the Monoid :)
For part 1, the choice of monoid is simple: our final action is a translation by a vector, so it makes sense that the intermediate actions would be vectors as well -- composing actions means adding those vectors together.
data Vector = V { dX :: Int, dY :: Int }
instance Semigroup Vector where
V dx dy <> V dx' dy' = V (dx + dx') (dy + dy')
instance Monoid Vector where
mempty = V 0 0
day02a :: String -> Int
day02a = day02
(\dx -> V dx 0) -- forward is just a horizontal vector
(\dy -> V 0 dy) -- up/down is a vertical vector
(\(V dx dy) (P x0 y0) -> P (x0 + dx) (y0 + dy))
Part 2 is a little trickier because we have to keep track of dx, dy and aim.
So we can think of our action as manipulating a Point
as well as an Aim
,
and combining them together.
newtype Aim = Aim Int
instance Semigroup Aim where
Aim a <> Aim b = Aim (a + b)
instance Monoid Aim where
mempty = Aim 0
So our "action" looks like:
data Part2Action = P2A { p2Vector :: Vector, p2Aim :: Aim }
However, it's not exactly obvious how to turn this into a monoid. How do we
combine two Part2Action
s to create a new one, in a way that respects the
logic of part 2? Simply adding them point-wise does not do the trick, because
we have to somehow also get the Aim
to factor into the new y value.
Group theory to the rescue! Using the monoid-extras library, we can
can say that Aim
encodes a "vector transformer". Applying an aim means adding
the dy value by the aim value multiplied the dx component.
instance Action Aim Vector where
act (Aim a) = moveDownByAimFactor
where
moveDownByAimFactor (V dx dy) = V dx (dy + a * dx)
Because of this, we can now pair together Vector
and Aim
as a semi-direct
product: If we pair up our monoid (Vector
) with a "point transformer"
(Aim
), then Semi Vector Aim
is a monoid that contains both (like our
Part2Action
above) but also provides a Monoid
instance that "does the right
thing" (adds vector, adds aim, and also makes sure the aim action gets applied
correctly when adding vectors) thanks to group theory.
-- constructors/deconstructors that monoid-extras gives us
inject :: Vector -> Semi Vector Aim
embed :: Aim -> Semi Vector Aim
untag :: Semi Vector Aim -> Vector
day02b :: String -> Int
day02b = day02
(\dx -> inject $ V dx 0) -- forward just contributs a vector motion
(\a -> embed $ Aim a ) -- up/down just adjusts the aim
(\sdp (P x0 y0) ->
let V dx dy = untag sdp
in P (x0 + dx) (y0 + dy)
)
And that's it, we're done, thanks to the power of group theory! We identified
that our final monoid must somehow contain both components (Vector
, and
Aim
), but did not know how the two could come together to form a mega-monoid
of both. However, because we saw that Aim
also gets accumulated while also
acting as a "point transformer", we can describe how it transforms points (with
the Action
instance) and so we can use Semi
(semi-direct product) to encode
our action with a Monoid
instance that does the right thing.
What was the point of this? Well, we were able to unify both parts 1 and 2 to
be solved in the same overall method, just by picking a different monoid for
each part. With only two parts, it might not seem that worth it to abstract,
but maybe if there were more we could experiment with what other neat monoids
we could express our solution as! But, a major advantage we reap now is that,
because each action combines into other actions (associatively), we could do
all of this in parallel! If our list of actions was very long, we could
distribute the work over multiple cores or computers and re-combine like a
map-reduce. There's just something very satisfying about having the "final
action" be of the same type as our intermediate actions. With that
revelation, we open the door to the entire field of monoid-based optimizations
and pre-made algorithms (like Semi
)
(Thanks to mniip
in libera irc's #adventofcode channel for helping me
express this in terms of a semi-direct product! My original attempt used a 4x4
matrix that ended up doing the same thing after some symbolic analysis.)
(Thanks too to @lysxia on twitter for pointing out a nicer way of interpreting the action in terms of how it acts on points!)
>> Day 02a
benchmarking...
time 8.030 μs (7.348 μs .. 8.777 μs)
0.955 R² (0.936 R² .. 0.993 R²)
mean 7.800 μs (7.516 μs .. 8.666 μs)
std dev 1.719 μs (936.6 ns .. 3.161 μs)
variance introduced by outliers: 97% (severely inflated)
* parsing and formatting times excluded
>> Day 02b
benchmarking...
time 1.710 ms (1.616 ms .. 1.830 ms)
0.964 R² (0.929 R² .. 0.987 R²)
mean 1.730 ms (1.673 ms .. 1.792 ms)
std dev 215.9 μs (168.1 μs .. 321.3 μs)
variance introduced by outliers: 79% (severely inflated)
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 03a
benchmarking...
time 1.556 ms (1.538 ms .. 1.577 ms)
0.998 R² (0.997 R² .. 0.999 R²)
mean 1.560 ms (1.545 ms .. 1.574 ms)
std dev 58.01 μs (47.70 μs .. 76.73 μs)
variance introduced by outliers: 25% (moderately inflated)
* parsing and formatting times excluded
>> Day 03b
benchmarking...
time 543.0 μs (527.5 μs .. 568.7 μs)
0.990 R² (0.977 R² .. 0.999 R²)
mean 534.3 μs (527.0 μs .. 551.6 μs)
std dev 33.62 μs (14.55 μs .. 58.08 μs)
variance introduced by outliers: 55% (severely inflated)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 04a
benchmarking...
time 298.3 μs (296.0 μs .. 301.3 μs)
1.000 R² (0.999 R² .. 1.000 R²)
mean 298.2 μs (297.3 μs .. 300.3 μs)
std dev 4.367 μs (2.639 μs .. 7.182 μs)
* parsing and formatting times excluded
>> Day 04b
benchmarking...
time 784.5 μs (773.9 μs .. 805.2 μs)
0.997 R² (0.994 R² .. 0.999 R²)
mean 752.0 μs (739.7 μs .. 773.0 μs)
std dev 53.03 μs (40.49 μs .. 74.21 μs)
variance introduced by outliers: 59% (severely inflated)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 05a
benchmarking...
time 5.197 ms (5.107 ms .. 5.278 ms)
0.997 R² (0.996 R² .. 0.999 R²)
mean 5.349 ms (5.294 ms .. 5.445 ms)
std dev 217.7 μs (188.5 μs .. 263.7 μs)
variance introduced by outliers: 21% (moderately inflated)
* parsing and formatting times excluded
>> Day 05b
benchmarking...
time 23.19 ms (23.07 ms .. 23.31 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 23.36 ms (23.25 ms .. 23.52 ms)
std dev 273.2 μs (200.5 μs .. 368.6 μs)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 06a
benchmarking...
time 3.421 μs (3.415 μs .. 3.430 μs)
1.000 R² (1.000 R² .. 1.000 R²)
mean 3.422 μs (3.414 μs .. 3.438 μs)
std dev 36.50 ns (17.25 ns .. 65.66 ns)
* parsing and formatting times excluded
>> Day 06b
benchmarking...
time 3.454 μs (3.432 μs .. 3.475 μs)
1.000 R² (0.999 R² .. 1.000 R²)
mean 3.457 μs (3.446 μs .. 3.477 μs)
std dev 54.27 ns (25.71 ns .. 94.82 ns)
variance introduced by outliers: 14% (moderately inflated)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 07a
benchmarking...
time 410.4 μs (407.7 μs .. 414.0 μs)
1.000 R² (0.999 R² .. 1.000 R²)
mean 409.6 μs (408.0 μs .. 412.3 μs)
std dev 7.499 μs (4.702 μs .. 12.56 μs)
* parsing and formatting times excluded
>> Day 07b
benchmarking...
time 451.6 μs (447.5 μs .. 456.9 μs)
0.999 R² (0.999 R² .. 1.000 R²)
mean 444.5 μs (442.3 μs .. 447.2 μs)
std dev 8.207 μs (6.712 μs .. 11.79 μs)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 08a
benchmarking...
time 28.48 μs (28.41 μs .. 28.55 μs)
1.000 R² (1.000 R² .. 1.000 R²)
mean 28.51 μs (28.43 μs .. 28.62 μs)
std dev 306.4 ns (233.2 ns .. 427.0 ns)
* parsing and formatting times excluded
>> Day 08b
benchmarking...
time 495.5 μs (494.3 μs .. 497.3 μs)
1.000 R² (1.000 R² .. 1.000 R²)
mean 498.1 μs (497.1 μs .. 500.1 μs)
std dev 5.367 μs (4.515 μs .. 6.275 μs)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 09a
benchmarking...
time 1.593 ms (1.573 ms .. 1.614 ms)
0.999 R² (0.999 R² .. 1.000 R²)
mean 1.570 ms (1.561 ms .. 1.578 ms)
std dev 27.83 μs (21.69 μs .. 34.24 μs)
* parsing and formatting times excluded
>> Day 09b
benchmarking...
time 7.241 ms (7.209 ms .. 7.271 ms)
1.000 R² (0.999 R² .. 1.000 R²)
mean 7.314 ms (7.292 ms .. 7.354 ms)
std dev 82.86 μs (58.27 μs .. 124.0 μs)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 10a
benchmarking...
time 66.77 μs (60.77 μs .. 70.87 μs)
0.974 R² (0.973 R² .. 0.985 R²)
mean 60.28 μs (58.91 μs .. 62.63 μs)
std dev 6.267 μs (4.142 μs .. 8.292 μs)
variance introduced by outliers: 84% (severely inflated)
* parsing and formatting times excluded
>> Day 10b
benchmarking...
time 67.56 μs (67.48 μs .. 67.65 μs)
1.000 R² (1.000 R² .. 1.000 R²)
mean 67.64 μs (67.51 μs .. 68.10 μs)
std dev 776.5 ns (188.6 ns .. 1.601 μs)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 11a
benchmarking...
time 7.176 ms (7.149 ms .. 7.204 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 7.130 ms (7.107 ms .. 7.161 ms)
std dev 70.69 μs (57.87 μs .. 88.48 μs)
* parsing and formatting times excluded
>> Day 11b
benchmarking...
time 15.22 ms (14.34 ms .. 16.29 ms)
0.988 R² (0.981 R² .. 0.998 R²)
mean 14.97 ms (14.69 ms .. 15.47 ms)
std dev 992.2 μs (555.8 μs .. 1.562 ms)
variance introduced by outliers: 31% (moderately inflated)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 15a
benchmarking...
time 61.55 ms (60.33 ms .. 62.69 ms)
0.999 R² (0.997 R² .. 1.000 R²)
mean 60.96 ms (60.51 ms .. 61.67 ms)
std dev 1.050 ms (753.2 μs .. 1.426 ms)
* parsing and formatting times excluded
>> Day 15b
benchmarking...
time 2.338 s (2.307 s .. 2.372 s)
1.000 R² (1.000 R² .. 1.000 R²)
mean 2.331 s (2.316 s .. 2.339 s)
std dev 14.04 ms (1.157 ms .. 17.83 ms)
variance introduced by outliers: 19% (moderately inflated)
* parsing and formatting times excluded
Prompt / Code / Rendered / Standalone Reflection Page
Reflection not yet written -- please check back later!
>> Day 16a
benchmarking...
time 1.420 ms (1.351 ms .. 1.535 ms)
0.977 R² (0.963 R² .. 0.995 R²)
mean 1.452 ms (1.363 ms .. 1.678 ms)
std dev 418.2 μs (84.22 μs .. 896.7 μs)
variance introduced by outliers: 97% (severely inflated)
* parsing and formatting times excluded
>> Day 16b
benchmarking...
time 1.091 ms (1.051 ms .. 1.145 ms)
0.990 R² (0.986 R² .. 0.996 R²)
mean 1.085 ms (1.061 ms .. 1.111 ms)
std dev 88.42 μs (51.04 μs .. 118.5 μs)
variance introduced by outliers: 64% (severely inflated)
* parsing and formatting times excluded