##### Day 3: `linear`

and `containers`

In this puzzle, we need to move a cursor in 2D space and count the number of distinct coordinates we visited.

```
import qualified Data.Set as Set
import Linear.V2
direction :: Char -> V2 Int
direction '<' = V2 (-1) 0
direction '>' = V2 1 0
direction '^' = V2 0 1
direction 'v' = V2 0 (-1)
direction _ = V2 0 0
-- |
-- >>> day3 ">"
-- 2
-- >>> day3 "^>v<"
-- 4
-- >>> day3 "^v^v^v^v^v"
-- 2
day3 = map direction
>>> scanl (+) 0
>>> Set.fromList
>>> length
```

I use `V2`

from the `linear`

library to represent the 2D coordinates, because its vector space addition allows me to add both coordinates at once. To get the list of visited coordinates, I use `scanl (+) 0`

again: it's still a running sum, but this time we're adding directional unit vectors to a coordinate which starts at "zero", the origin of the 2D plane. This works because `(+)`

and `0`

are obtained via methods of the `Num`

type class, and Haskell infers that I want to use the `Num`

instance for `V2 Int`

.

Interestingly, this is a parameterized instance, which means that the `Num`

instance for `V2 Int`

delegates part of its implementation to the `Num`

instance for `Int`

, and similarly for `V2 Float`

, etc. This mechanism allows type class instances to be automatically constructed for complex composed types which have never been seen before, without requiring the programmer to write a type class instance for this particular composition of types. For example, our `MyEDSL`

from earlier was composed of four layers of monad transformers, each of which defined their own `Monad`

instance, but we did not have to define a `Monad`

instance for `MyEDSL`

in order to be able to use `forM_`

and `when`

.

Next, to obtain distinct coordinates from the list of all coordinates, I convert the list into a set, defined by the `containers`

library. There is also a function called `nub`

in the standard library which eliminates duplicates from a list, but for technical reasons `nub`

executes in quadratic time while `Set.fromList`

runs in `O(n*log n)`

. The `containers`

library also has an even more efficient `IntSet`

which can only contain `Int`

s, but I rarely use it because `Set Int`

is more versatile (I can easily switch to a `Set Float`

if I need to) and it's fast enough for me.

Finally, I compute the number of elements in the set using `length`

. It's the same `length`

we used earlier to compute the size of a list because, you guessed it, `length`

is part of yet another type class. It's a type class called `Foldable`

, and its methods include or can be used to derive `length`

, `sum`

, `forM_`

, and other functions which require iterating over the elements of a container without modifying them.

##### Day 3.5: infinite lists, `lens`

, and constraints

For the second part of the puzzle, we now need to move two cursors, and the ASCII instructions alternate between giving a direction for the first cursor and giving a direction for the second. We need to count the number of distinct coordinates which at least one of the cursors has visited.

```
{-# LANGUAGE RankNTypes #-}
import Control.Lens
import Data.Set.Lens
import Data.List
day3_5 = map direction
>>> zip [0..]
>>> partition (fst >>> even)
>>> over (both.mapped) snd
>>> over both (scanl (+) 0)
>>> setOf (both.folded)
>>> length
```

The sequence of functions is getting a bit long, so let's split it into smaller helper functions.

```
-- |
-- >>> fromAlternating [1..6]
-- ([1,3,5],[2,4,6])
-- >>> fromAlternating "a1b2c3"
-- ("abc","123")
fromAlternating :: [a] -> ([a], [a])
fromAlternating = zip [0..]
>>> partition (fst >>> even)
>>> over (both.mapped) snd
```

This first helper splits the lists of instructions into two sublists, one for each cursor. To do that, we first number the elements of the list from `0`

to `n`

, then we partition the list into two sublists according to whether the element number is even or odd, and finally, we drop the element numbers and keep only the elements.

`zip [0..]`

is a common Haskell idiom for numbering the elements of a list. The way it works is by creating the infinite list `[0,1,2,...]`

and using `zip`

to pair each element of this list with each element of the original list `[x,y,z]`

, resulting in the list of pairs `[(0,x), (1,y), (2,z)]`

. The resulting list has only three elements, not an inifinite number, because `zip`

stops after the shortest list is exhausted. Infinite structures are very common in Haskell, because they allow us to focus on the contents and not on the boundary conditions: in this case, we focus on the fact that the list contains an increasing list of integers, and we don't bother specifying that we need as many integers as there are elements in our original list. Haskell's laziness makes sure that it will not get stuck trying to allocate an infinite list, and will instead only allocate as much as is necessary to compute the resulting finite list of pairs.

`partition`

is a function from the standard library which partitions a list into two sublists according to a predicate: one sublist contains the elements which satisfy the predicate, and the other sublist contains the elements which don't. In this case, our predicate is that the first element of the pair, that is, the element number, should be even. This causes the even-numbered pairs to end up in the first list, and the odd-numbered pairs to end up in the second.

As this point we have two lists of pairs, with each pair containing a number and an element. We'd rather have lists of elements, because the numbers are just an intermediate annotation we used for the partitioning. So we'd like to apply `snd`

, the function which only keeps the second part of a pair, to all the elements of both lists. I could have writen `(\(xs,ys) -> (map snd xs, map snd ys))`

, it would have done the job, but I'd rather to take the opportunity to demonstrate the `lens`

library.

The `lens`

library deals with nested structures, in this case a pair of lists of elements. I think the name comes from the fact that a real-world lens allows you to focus light on a smaller area, and that you can compose multiple lenses to focus the light on an even smaller area. Similarly, a `Lens`

from the `lens`

library allows you to manipulate a structure by focusing on manipulating a smaller part of that nested structure, and `Lens`

es can be composed to allow you to focus on a deeply-nested element hidden deep inside a nested structure.

The `lens`

library also offers a variety of other "optics", which can focus on something other than a single element. In this case, I am constructing a `Setter`

, which allows multiple elements in a nested structure to be modified at the same time. I am constructing it by composing `both`

, a setter which focuses on both halves of a pair simultaneously, with `mapped`

, a setter which focuses on all the elements of a list. The net result is that I am focusing on all the elements of both of my lists, that is, I am focusing on all the pairs on which I want to apply `snd`

.

```
-- |
-- >>> runningSum [1,2,3]
-- [0,1,3,6]
-- >>> runningSum (map direction "^>>v")
-- [V2 0 0,V2 0 1,V2 1 1,V2 2 1,V2 2 0]
runningSum :: Num a => [a] -> [a]
runningSum = scanl (+) 0
```

This second helper is our good friend `scanl (+) 0`

, which we have used to produce running sums of integers and running sums of 2D coordinates. The `Num a`

constraint in the type signature indicates that while this helper function can indeed work on list of elements of various types, it will only work if that element type has a `Num`

instance.

```
-- |
-- >>> distinctCountOf folded [1,2,3,2,2,3]
-- 3
-- >>> distinctCountOf both (1,2)
-- 2
-- >>> distinctCountOf both (1,1)
-- 1
distinctCountOf :: Ord a => Fold s a -> s -> Int
distinctCountOf elems = setOf elems
>>> length
```

Our final helper is a generalization of the `Set.fromList >>> length`

technique we've been using so far to count the distinct elements of a list. This time, however, the elements we want to count are distributed among two separate lists, so a simple `Set.fromList`

is not going to work. Instead, I'm using `setOf`

, a function from `lens`

which takes a `Fold`

and constructs a `Set`

out of the elements on which the `Fold`

focuses. A `Fold`

is yet another kind of optic, one which also focuses on multiple elements simultaneously, but instead of modifying all of them at once like a `Setter`

, it examines them using methods from `Foldable`

.

```
-- |
-- >>> day3_5 "^v"
-- 3
-- >>> day3_5 "^>v<"
-- 3
-- >>> day3_5 "^v^v^v^v^v"
-- 11
day3_5 = map direction
>>> fromAlternating
>>> over both runningSum
>>> distinctCountOf (both.folded)
```

Finally, we compose our helpers into one sophisticated function which solves the puzzle: we parse the directions into unit vectors, we split the alternating directions into one list for each of the two cursors, we list the visited coordinates for each, and we count how many distinct coordinates have been visited overall.

##### Navigation

- About this series
- Previous day: type inference and type classes
- Next day: external programs and streaming

## No comments:

Post a comment