Skip to content
This repository has been archived by the owner on Nov 17, 2024. It is now read-only.

Latest commit

 

History

History
780 lines (583 loc) · 25.9 KB

reflections.md

File metadata and controls

780 lines (583 loc) · 25.9 KB

Reflections

2016 / 2017 / 2018 / 2019 / 2020 / 2021

Available as an RSS Feed

Table of Contents

Day 1

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 1 Benchmarks

>> 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

Day 2

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 Part2Actions 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 2 Benchmarks

>> 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)

Day 3

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 3 Benchmarks

>> 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

Day 4

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 4 Benchmarks

>> 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

Day 5

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 5 Benchmarks

>> 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

Day 6

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 6 Benchmarks

>> 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

Day 7

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 7 Benchmarks

>> 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

Day 8

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 8 Benchmarks

>> 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

Day 9

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 9 Benchmarks

>> 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

Day 10

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 10 Benchmarks

>> 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

Day 11

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 11 Benchmarks

>> 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

Day 15

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 15 Benchmarks

>> 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

Day 16

Prompt / Code / Rendered / Standalone Reflection Page

Reflection not yet written -- please check back later!

Day 16 Benchmarks

>> 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