all / 1 / 2 / 3 / 4 / 5 / 6 / 7 / 8 / 9 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17 / 18 / 19 / 20 / 21 / 22 / 23 / 24 / 25
I guess today is a "here's the algorithm, now implement it" puzzle, to contrast/take a break from yesterday's "here's the goal, figure out the algorithm" :)
First, let's start with an intermediate data type representing the actions possible on each line:
data Instr = Mask [Maybe Bool] | Write Int Int
The mask will be a list of
Maybe Bool, where
Just False, and
Just True. However, it's important to reverse the string
when parsing it from the input, because we want index
0 to correspond to bit
1 to correspond to bit
1, etc., to make our lives easier.
That's because we can implement the application of a mask (for part 1) using
a version of
foldl' that gives you an item's index as you are folding it:
import Data.Bits (clearBit, setBit) import Control.Lens.Indexed (ifoldl') applyMask1 :: Int -> [Maybe Bool] -> Int applyMask1 = ifoldl' $ \i x -> \case Nothing -> x Just False -> clearBit x i Just True -> setBit x i
If the bit list contains a
Nothing in a given index, leave the item
unchanged. If it contains a
Just False, clear that index's bit (set it to
zero). If it contains a
Just Nothing, set that index's bit (set it to one).
And that leaves part 1 as a foldl through all the instructions, keeping the current map and mask as state:
import Data.IntMap (IntMap) import qualified Data.IntMap as IM part1 :: [Instr] -> (IntMap Int, [Maybe Bool]) part1 = foldl' go (IM.empty, ) where go :: (IntMap Int, [Maybe Bool]) -> Instr -> (IntMap Int, [Maybe Bool]) go (!mp, !msk) = \case Mask msk' -> (mp, msk') Write addr n -> let mp' = IM.insert addr (applyMask1 n msk) mp in (mp', msk)
Part 2's mask application is interesting, because it lives in
"non-determinancy". Basically, each bit mask bit application could potentially
yield multiple possibilities. We have to accumulate every nested possibility.
This feature is given to us by list's
Monad instance, so we can swap
ifoldl' :: (Int -> b -> a -> b) -> b -> [a] -> b ifoldlM :: (Int -> b -> a -> m b) -> b -> [a] -> m b
ifoldlM, each result lives in monad
m, so the semantics of "proceeding
along the fold" are deferred to the
Monad instance for
Maybe, it means that you only proceed if you get a
Just, or else
IO, it means that proceeding
involves chaining the IO action's execution and binding the result to give it
to the function's next iteration. If
 (list), it means that
subsequent chaining will run the function on every possibility returned by
the function's previous call, accumulating every possible way of choosing every
possible choice. (I talked about this in more depth in one of my first ever
import Control.Lens.Indexed (ifoldlM) applyMask2 :: Int -> [Maybe Bool] -> [Int] applyMask2 = ifoldlM $ \i x -> \case Nothing -> [clearBit x i, setBit x i] Just False -> [x] Just True -> [setBit x i]
For these, we return a list of every possible change from a given bit mask bit.
Nothing "floating" case, there are two possibilities; for the other
two, there is only one. We trust list's
Monad instance to properly thread
over all possible results into a list of all possible changes that that
could have been subjected to.
And so, part 2 looks a lot like part 1!
part2 :: [Instr] -> (IntMap Int, [Maybe Bool]) part2 = foldl' go (IM.empty, ) where go :: (IntMap Int, [Maybe Bool]) -> Instr -> (IntMap Int, [Maybe Bool]) go (!mp, !msk) = \case Mask msk' -> (mp, msk') Write addr n -> let newMp = IM.fromList ((,n) <$> applyMask2 addr msk) in (newMp <> mp, msk)
(<>) here is a left-biased merger, so it merges in all of the newly seen
indices into the existing ones.
Back to all reflections for 2020
Day 14 Benchmarks
>> Day 14a benchmarking... time 158.7 μs (158.0 μs .. 159.4 μs) 0.999 R² (0.997 R² .. 1.000 R²) mean 157.9 μs (157.6 μs .. 158.6 μs) std dev 1.293 μs (845.8 ns .. 2.372 μs) * parsing and formatting times excluded >> Day 14b benchmarking... time 25.76 ms (24.66 ms .. 27.04 ms) 0.990 R² (0.979 R² .. 0.998 R²) mean 25.49 ms (25.02 ms .. 26.27 ms) std dev 1.358 ms (982.2 μs .. 1.914 ms) variance introduced by outliers: 20% (moderately inflated) * parsing and formatting times excluded