Reformat markdown

This commit is contained in:
Nathan McCarty 2025-01-14 15:15:09 -05:00
parent 5a313c952f
commit b0e7c1aa91
14 changed files with 459 additions and 188 deletions

View file

@ -5,6 +5,7 @@ import Structures.Dependent.FreshList
import Runner
```
<!-- idris
import Years.Y2015.Day1
import Years.Y2015.Day2
@ -68,4 +69,3 @@ y2015 = MkYear 2015 [
```idris
]
```

View file

@ -1,6 +1,7 @@
# Year 2015 Day 1
Pretty simple, basic warmup problem, nothing really novel is on display here except the effectful part computations.
Pretty simple, basic warmup problem, nothing really novel is on display here
except the effectful part computations.
<!-- idris
module Years.Y2015.Day1
@ -14,9 +15,11 @@ import Runner
## Solver Functions
This one implements the entirety of the logic for part 1, in a simple tail recursive manner, pattern matching on each character in the input string.
This one implements the entirety of the logic for part 1, in a simple tail
recursive manner, pattern matching on each character in the input string.
We include a case for a non-paren character for totality's sake, but since we kinda trust the input here we just don't do anything in that branch.
We include a case for a non-paren character for totality's sake, but since we
kinda trust the input here we just don't do anything in that branch.
```idris
trackFloor : (start : Integer) -> (xs : List Char) -> Integer
@ -26,7 +29,9 @@ trackFloor start (')' :: xs) = trackFloor (start - 1) xs
trackFloor start (x :: xs) = trackFloor start xs
```
This one is slightly more complicated, ultimately very similar to the above, but with two accumulators, one for the position in the input string in addition to the one for the current floor.
This one is slightly more complicated, ultimately very similar to the above, but
with two accumulators, one for the position in the input string in addition to
the one for the current floor.
```idris
findBasement : (position : Nat) -> (currentFloor : Integer) -> (xs : List Char)
@ -48,7 +53,8 @@ Both this parts are simple application of one of our solver functions
### Part 1
Very uneventful, the only thing novel here is pulling the input out of the `ReaderL "input" String`, which will become very boring very quickly.
Very uneventful, the only thing novel here is pulling the input out of the
`ReaderL "input" String`, which will become very boring very quickly.
```idris
part1 : Eff (PartEff String) (Integer, ())
@ -60,7 +66,8 @@ part1 = do
### Part 2
We have to be careful to start the position accumulator at 1, since the problem specifies that the input string is 1-index.
We have to be careful to start the position accumulator at 1, since the problem
specifies that the input string is 1-index.
```idris
part2 : () -> Eff (PartEff String) Nat

View file

@ -32,7 +32,9 @@ record Box where
### Box methods
`.area` provides the surface area of the box, `.slack` provides the surface area of the smallest face, `.ribbon` provides the smallest perimeter of a face, and `.bow` provides the volume of the box.
`.area` provides the surface area of the box, `.slack` provides the surface area
of the smallest face, `.ribbon` provides the smallest perimeter of a face, and
`.bow` provides the volume of the box.
Names are as described in the problem
@ -53,14 +55,16 @@ Names are as described in the problem
(.bow) (MkBox length width height) = length * width * height
```
Provide the total amount of ribbon needed, by adding the ribbon (smallest perimeter) and the bow (volume) values together.
Provide the total amount of ribbon needed, by adding the ribbon (smallest
perimeter) and the bow (volume) values together.
```idris
totalRibbon : Box -> Integer
totalRibbon x = x.ribbon + x.bow
```
Provide the total amount of paper needed by adding the surface area and the slack (smallest side surface area) together.
Provide the total amount of paper needed by adding the surface area and the
slack (smallest side surface area) together.
```idris
paperNeeded : Box -> Integer
@ -77,14 +81,17 @@ parseBox : Has (Except String) fs =>
parseBox str = do
```
First, we split the string into 3 parts by pattern matching on the result of `split`, using `pure` to lift the computation into the effect, and throwing an error if the pattern match fails.
First, we split the string into 3 parts by pattern matching on the result of
`split`, using `pure` to lift the computation into the effect, and throwing an
error if the pattern match fails.
```idris
l ::: [w, h] <- pure $ split (== 'x') str
| xs => throw "Box did not have exactly 3 components: \{show xs}"
```
Then try to parse each of the three integers, throwing an error if parsing fails, then construct and return our box.
Then try to parse each of the three integers, throwing an error if parsing
fails, then construct and return our box.
```idris
length <- note "Failed parsing length: \{show l}" $ parsePositive l
@ -97,9 +104,11 @@ Then try to parse each of the three integers, throwing an error if parsing fails
### Part 1
Split the input into lines, effectfully traverse our box parser over the resulting list of lines, then sum the amounts of paper needed for each box.
Split the input into lines, effectfully traverse our box parser over the
resulting list of lines, then sum the amounts of paper needed for each box.
We return the list of parsed boxes as the context here to avoid needing to parse again in part 2
We return the list of parsed boxes as the context here to avoid needing to parse
again in part 2
```idris
part1 : Eff (PartEff String) (Integer, List Box)
@ -112,7 +121,9 @@ part1 = do
### Part 2
Much the same as part 1, except with the amount of ribbon needed instead of the amount of paper, and without the parsing, since we received the already parsed list of boxes from part 1 as our context value.
Much the same as part 1, except with the amount of ribbon needed instead of the
amount of paper, and without the parsing, since we received the already parsed
list of boxes from part 1 as our context value.
```idris
part2 : (boxes : List Box) -> Eff (PartEff String) Integer

View file

@ -1,6 +1,7 @@
# Year 2015 Day 3
This day provides a gentle introduction to `mutual` blocks and mutually recursive functions.
This day provides a gentle introduction to `mutual` blocks and mutually
recursive functions.
<!-- idris
module Years.Y2015.Day3
@ -23,13 +24,16 @@ import Util
## Parsing and data structures
We'll do parsing a little more properly this time, turning the input into a list of movement commands
We'll do parsing a little more properly this time, turning the input into a list
of movement commands
```idris
data Movement = North | East | South | West
```
We need an effectful operation to parse a single char into a movement. We'll pattern match on the char, and include a catch-all case that throws an error in the event of an invalid char
We need an effectful operation to parse a single char into a movement. We'll
pattern match on the char, and include a catch-all case that throws an error in
the event of an invalid char
```idris
parseMovement : Has (Except String) fs => (x : Char) -> Eff fs Movement
@ -40,7 +44,9 @@ parseMovement '<' = pure West
parseMovement x = throw "Invalid Movement: \{show x}"
```
We also need to be able to translate a `Movement` into a vector of length one pointing in the given direction in coordinate space. Somewhat arbitrarily, we chose 'North' to be positive x and 'East' to be positive y.
We also need to be able to translate a `Movement` into a vector of length one
pointing in the given direction in coordinate space. Somewhat arbitrarily, we
chose 'North' to be positive x and 'East' to be positive y.
```idris
vector : Movement -> (Integer, Integer)
@ -54,11 +60,17 @@ vector West = (0, -1)
### Visited houses
This is a pretty simple task, we are just applying the movements to our current position, and adding our current position to the set of visited locations, so we'll handle this with a normal tail recursive function.
This is a pretty simple task, we are just applying the movements to our current
position, and adding our current position to the set of visited locations, so
we'll handle this with a normal tail recursive function.
To keep the api nice, we wont ask for the set or the starting location in the top-level function, and instead have the top level function initialize the set and location before passing control to the inner tail-recursive variant.
To keep the api nice, we wont ask for the set or the starting location in the
top-level function, and instead have the top level function initialize the set
and location before passing control to the inner tail-recursive variant.
Because the starting location gets a present, we'll add our location to the set before performing the movement, so we will need to add our final location to the set in the recursive base case.
Because the starting location gets a present, we'll add our location to the set
before performing the movement, so we will need to add our final location to the
set in the recursive base case.
```idris
visitedLocations : List Movement -> SortedSet (Integer, Integer)
@ -73,13 +85,26 @@ visitedLocations xs = visitor xs empty (0, 0)
### Robo Santa
This one gets a bit more interesting, we'll adopt the same tail recursive approach, but instead use a `mutual` block and two mutually recursive functions to handle the alternation between santa and robo santa. The `visitSanta` function will pass control to `visitRobo` after executing its movement, and vise versa.
This one gets a bit more interesting, we'll adopt the same tail recursive
approach, but instead use a `mutual` block and two mutually recursive functions
to handle the alternation between santa and robo santa. The `visitSanta`
function will pass control to `visitRobo` after executing its movement, and vise
versa.
We'll want to insert both present deliverer's locations in the recursive base case, this may result in a duplicate location, but that's okay because `SortedSet` will only hold at most one of each item inserted into it.
We'll want to insert both present deliverer's locations in the recursive base
case, this may result in a duplicate location, but that's okay because
`SortedSet` will only hold at most one of each item inserted into it.
In idris, there is a general requirement that values be defined before their use, a common feature of dependently typed languages, resulting from the fact that just having the type signature of a function/value alone is not always enough to perform type checking, as functions can appear as part of types, requiring evaluation of the function and making automatic dependency analysis effectively impossible.
In idris, there is a general requirement that values be defined before their
use, a common feature of dependently typed languages, resulting from the fact
that just having the type signature of a function/value alone is not always
enough to perform type checking, as functions can appear as part of types,
requiring evaluation of the function and making automatic dependency analysis
effectively impossible.
Inside a `mutual` block, elaboration behaves differently, elaborating types first and then values in separate passes. This restricts what you can do a little, but enables mutually recursive functions.
Inside a `mutual` block, elaboration behaves differently, elaborating types
first and then values in separate passes. This restricts what you can do a
little, but enables mutually recursive functions.
```idris
visitedLocations' : List Movement -> SortedSet (Integer, Integer)
@ -103,7 +128,9 @@ visitedLocations' xs = visitSanta xs empty (0, 0) (0, 0)
### Part 1
Similar to the previous day, we get our input, unpack it, and traverse our effectful movement parsing function over it, before feeding that into our solving function.
Similar to the previous day, we get our input, unpack it, and traverse our
effectful movement parsing function over it, before feeding that into our
solving function.
```idris
part1 : Eff (PartEff String) (Nat, List Movement)

View file

@ -1,6 +1,7 @@
# Year 2015 Day 4
This day introduces us to a little bit of FFI, linking to openssl to use it's `MD5` functionality.
This day introduces us to a little bit of FFI, linking to openssl to use it's
`MD5` functionality.
<!-- idris
module Years.Y2015.Day4
@ -19,15 +20,22 @@ import Prim.Array
## FFI
We will be using openssl's `MD5` function to do the hashing, so we will need to provide a couple of foreign function definitions.
We will be using openssl's `MD5` function to do the hashing, so we will need to
provide a couple of foreign function definitions.
First, we'll provide one for the C standard library's `strlen`. We could get this value from within idris, but we'll do it through FFI to flesh out the example a bit more.
First, we'll provide one for the C standard library's `strlen`. We could get
this value from within idris, but we'll do it through FFI to flesh out the
example a bit more.
The format of the specifier after `%foreign` is "language:function name,library name". Since `strlen` is the c standard library, we use `libc` as the library name.
The format of the specifier after `%foreign` is "language:function name,library
name". Since `strlen` is the c standard library, we use `libc` as the library
name.
Idris will automatically convert primitive types like `String`s and `Int`s to their C equivalents at the FFI boundary.
Idris will automatically convert primitive types like `String`s and `Int`s to
their C equivalents at the FFI boundary.
`PrimIO` is a special, basic type of IO action based on linear types, we won't be getting into the specifics right now.
`PrimIO` is a special, basic type of IO action based on linear types, we won't
be getting into the specifics right now.
By convention, primitive FFI function names start with `prim__` in idris.
@ -36,9 +44,12 @@ By convention, primitive FFI function names start with `prim__` in idris.
prim__strlen : String -> PrimIO Int
```
Now we need one for the `MD5` function from openssl, the openssl objectfile calls itself `libcrypto`, so we'll use that as the library name.
Now we need one for the `MD5` function from openssl, the openssl objectfile
calls itself `libcrypto`, so we'll use that as the library name.
The `MD5` function actually returns a pointer to the hash value, but since the 3rd argument is the pointer it expects to write the hash to, we already have that value and we'll ignore it by having our `prim__md5` return unit.
The `MD5` function actually returns a pointer to the hash value, but since the
3rd argument is the pointer it expects to write the hash to, we already have
that value and we'll ignore it by having our `prim__md5` return unit.
The second argument is the length of the input string in bytes.
@ -47,15 +58,25 @@ The second argument is the length of the input string in bytes.
prim__md5 : String -> Int -> Ptr Bits8 -> PrimIO ()
```
Now we glue these parts together in our higher level `md5'` function.
Now we glue these parts together in our higher level `md5'` function.
We use `malloc` from `System.FFI` to allocate a buffer for `prim__md5` to write to. This returns an `AnyPtr`, when we need a `Ptr Bits8`, so we'll need to cast it, using `prim__castPtr` from the prelude.
We use `malloc` from `System.FFI` to allocate a buffer for `prim__md5` to write
to. This returns an `AnyPtr`, when we need a `Ptr Bits8`, so we'll need to cast
it, using `prim__castPtr` from the prelude.
We then use our `prim__strlen` function to get the length of our input string from within C, using `primIO` from the prelude to "lift" the resulting `PrimIO Int` into our `io` context.
We then use our `prim__strlen` function to get the length of our input string
from within C, using `primIO` from the prelude to "lift" the resulting
`PrimIO Int` into our `io` context.
We then feed the string, the calculated length, and our casted buffer into our `prim__md5` function, then use `prim__getArrayBits8` from the [c-ffi](https://github.com/joelberkeley/c-ffi/) library to extract each of the bytes, passing them into a pure idris helper function to convert into hex representation.
We then feed the string, the calculated length, and our casted buffer into our
`prim__md5` function, then use `prim__getArrayBits8` from the
[c-ffi](https://github.com/joelberkeley/c-ffi/) library to extract each of the
bytes, passing them into a pure idris helper function to convert into hex
representation.
Since we are working with C FFI and performing a little bit of manual memory management here, we must remember to `free` our pointer, and then we can splice together our output string and return.
Since we are working with C FFI and performing a little bit of manual memory
management here, we must remember to `free` our pointer, and then we can splice
together our output string and return.
```idris
md5' : HasIO io => String -> io String
@ -77,21 +98,38 @@ md5' str = do
pack [hexDigit (n `shiftR` 4), hexDigit (n .&. 0xF)]
```
Now, because foreign functions return a `PrimIO` by default, our `md'` still requires some sort of `HasIO` (like IO). This is undesirable here, after all, MD5 is a hash function, so it really _ought_ to behave like a pure function. We know from taking a careful look at what FFI functions we are invoking that we aren't altering any global state.
Now, because foreign functions return a `PrimIO` by default, our `md'` still
requires some sort of `HasIO` (like IO). This is undesirable here, after all,
MD5 is a hash function, so it really _ought_ to behave like a pure function. We
know from taking a careful look at what FFI functions we are invoking that we
aren't altering any global state.
Idris has an escape hatch for these types of situations, `unsafePerformIO`. This function is, as the name suggests, _quite_ unsafe, so it shouldn't be used if you don't fully understand the implications, and only sparingly at that, but doing FFI to C is an intrinsically unsafe operation, so it's morally okay to use here, as long as we are careful to not violate referential transparency or type safety. Haskell programmers should note that `unsafePerformIO` is _much_ easier to use correctly in Idris, largely as a result of Idris being strict by default.
Idris has an escape hatch for these types of situations, `unsafePerformIO`. This
function is, as the name suggests, _quite_ unsafe, so it shouldn't be used if
you don't fully understand the implications, and only sparingly at that, but
doing FFI to C is an intrinsically unsafe operation, so it's morally okay to use
here, as long as we are careful to not violate referential transparency or type
safety. Haskell programmers should note that `unsafePerformIO` is _much_ easier
to use correctly in Idris, largely as a result of Idris being strict by default.
Finally, we'll construct our top level `md5` function, wrapping `md5'` in `unsafePerformIO`
Finally, we'll construct our top level `md5` function, wrapping `md5'` in
`unsafePerformIO`
```idris
md5 : String -> String
md5 str = unsafePerformIO $ md5' str
```
## Solver functions
### Count the leading zeros in a string
There is a more clever way to do this making use of a "view" strings have for interacting with them as if they were lazy lists, but we've already covered so much here that we'll save that one for another time and just do it the straight forward way, having a top level function that accepts a string, turns it into a list of chars, and then pass it into a tail recursive helper function to actually count the zeros.
There is a more clever way to do this making use of a "view" strings have for
interacting with them as if they were lazy lists, but we've already covered so
much here that we'll save that one for another time and just do it the straight
forward way, having a top level function that accepts a string, turns it into a
list of chars, and then pass it into a tail recursive helper function to
actually count the zeros.
```idris
countZeros : String -> Nat
@ -116,7 +154,10 @@ checkZeros key number =
### Find the first input with a specific number of zeros
Search numbers starting at the provided seed and counting up in a tail recursive helper function. This function is going to be effectively impossible to prove totality for, so we have removed our normal `%default total` annotation for this file
Search numbers starting at the provided seed and counting up in a tail recursive
helper function. This function is going to be effectively impossible to prove
totality for, so we have removed our normal `%default total` annotation for this
file
```idris
findFirst : (zeros : Nat) -> (key : String) -> (seed : Nat) -> Nat
@ -130,7 +171,10 @@ findFirst zeros key current =
### Part 1
Pass the input into our `findFirst` function and use the result as both our output and context value. Since part2 is searching for a longer run of 0s than part 1, numbers already checked by part 1 can not possibly be valid solutions for part 2, so we can skip them in part 2.
Pass the input into our `findFirst` function and use the result as both our
output and context value. Since part2 is searching for a longer run of 0s than
part 1, numbers already checked by part 1 can not possibly be valid solutions
for part 2, so we can skip them in part 2.
```idris
part1 : Eff (PartEff String) (Nat, Nat)

View file

@ -1,6 +1,10 @@
# Year 2015 Day 5
This day provides a nice chance to introduce [views](https://idris2.readthedocs.io/en/latest/tutorial/views.html), specifically `String`'s [`AsList`](https://www.idris-lang.org/docs/idris2/current/base_docs/docs/Data.String.html#Data.String.AsList) view, which lets us treat `String`s as if they were lazy lists or iterators.
This day provides a nice chance to introduce
[views](https://idris2.readthedocs.io/en/latest/tutorial/views.html),
specifically `String`'s
[`AsList`](https://www.idris-lang.org/docs/idris2/current/base_docs/docs/Data.String.html#Data.String.AsList)
view, which lets us treat `String`s as if they were lazy lists or iterators.
<!-- idris
module Years.Y2015.Day5
@ -31,19 +35,39 @@ vowels : List Char
vowels = ['a', 'e', 'i', 'o', 'u']
```
Now define a function to check if a string contains a minimum number of vowels. Here we make use of the `with` rule and the `AsList` view over strings.
Now define a function to check if a string contains a minimum number of vowels.
Here we make use of the `with` rule and the `AsList` view over strings.
The `with` rule provides dependently typed pattern matching, allowing us to make use of the fact that the form of some of the arguments can be determined by the values of the others.
The `with` rule provides dependently typed pattern matching, allowing us to make
use of the fact that the form of some of the arguments can be determined by the
values of the others.
The `AsList` view associates a `String` with a lazy list of the characters in the string by providing incremental proofs that the `String` decomposes into the lazy list. An `AsList` of value `Nil`/`[]` proves that the associated string is empty, and an `AsList` of value `c :: xs` proves that the associated string can be generated by `strCons`ing the character `c` onto the rest of the string as captured by the `AsList` in `xs`.
The `AsList` view associates a `String` with a lazy list of the characters in
the string by providing incremental proofs that the `String` decomposes into the
lazy list. An `AsList` of value `Nil`/`[]` proves that the associated string is
empty, and an `AsList` of value `c :: xs` proves that the associated string can
be generated by `strCons`ing the character `c` onto the rest of the string as
captured by the `AsList` in `xs`.
We use the `asList` function, which generates the `AsList` view for the provided string, as the argument to the `with` block. Functions like this that produce views for a value are refereed to as "covering functions". In general, views are dependently typed constructs whose value provides insight into the structure of another value.
We use the `asList` function, which generates the `AsList` view for the provided
string, as the argument to the `with` block. Functions like this that produce
views for a value are refereed to as "covering functions". In general, views are
dependently typed constructs whose value provides insight into the structure of
another value.
We implement this function by keeping a counter of the number of vowels we still need to see to accept the string, returning true when that counter equals zero, or false if we hit the end of the string (when the string argument is empty and the associated `AsList` is `Nil`) while the counter is still non-zero.
We implement this function by keeping a counter of the number of vowels we still
need to see to accept the string, returning true when that counter equals zero,
or false if we hit the end of the string (when the string argument is empty and
the associated `AsList` is `Nil`) while the counter is still non-zero.
If the counter is non-zero and the string is non-empty, we check the current `c` character to see if it is a vowel, by checking to see if our `vowels` list contains it, then recurse, decrementing the counter if it was a vowel, and leaving the counter unchanged if it wasn't.
If the counter is non-zero and the string is non-empty, we check the current `c`
character to see if it is a vowel, by checking to see if our `vowels` list
contains it, then recurse, decrementing the counter if it was a vowel, and
leaving the counter unchanged if it wasn't.
The use of `| xs` after the recursive call to `containsVowels` tells Idris to recurse directly to the `with` block, allowing it to reuse the `AsList` value in `xs` without having to generate the `AsList` view again.
The use of `| xs` after the recursive call to `containsVowels` tells Idris to
recurse directly to the `with` block, allowing it to reuse the `AsList` value in
`xs` without having to generate the `AsList` view again.
```idris
containsVowels : (count : Nat) -> String -> Bool
@ -58,11 +82,18 @@ containsVowels count str with (asList str)
### At least one letter appears twice in a row
For this one, since we need to inspect two letters at a time, we break out a nested `with` block, with the tail of the `AsList` as the argument of the block, to also pattern match on the next character of the string.
For this one, since we need to inspect two letters at a time, we break out a
nested `with` block, with the tail of the `AsList` as the argument of the block,
to also pattern match on the next character of the string.
Since the `AsList` view is lazy, we need to use `force` in the inner with block to force the evaluation of the next component of the view, this is due to a [bug in the compiler](https://github.com/idris-lang/Idris2/issues/3461).
Since the `AsList` view is lazy, we need to use `force` in the inner with block
to force the evaluation of the next component of the view, this is due to a
[bug in the compiler](https://github.com/idris-lang/Idris2/issues/3461).
The logic here is pretty simple, if we hit the empty string or get down to one character, return `False`, if we have two characters available, compare them, returning `True` if they match, and recursing to the outer with block, only consuming one of the two characters, if they don't match.
The logic here is pretty simple, if we hit the empty string or get down to one
character, return `False`, if we have two characters available, compare them,
returning `True` if they match, and recursing to the outer with block, only
consuming one of the two characters, if they don't match.
```idris
doubleLetter : String -> Bool
@ -85,7 +116,10 @@ disallowedSubstrs : List String
disallowedSubstrs = ["ab", "cd", "pq", "xy"]
```
Idris's standard library does not currently define a function to check if a string is a substring of another, so we instead use our own `isSubstr` function, defined via use of the `AsList` view, from our [Util](../../Util.md#issubstr) module.
Idris's standard library does not currently define a function to check if a
string is a substring of another, so we instead use our own `isSubstr` function,
defined via use of the `AsList` view, from our [Util](../../Util.md#issubstr)
module.
```idris
noDisallowed : String -> Bool
@ -95,7 +129,8 @@ noDisallowed str =
### isNice
Combine all three of our above functions together to check if a string is a "nice" string
Combine all three of our above functions together to check if a string is a
"nice" string
```idris
isNice : String -> Bool
@ -106,7 +141,9 @@ isNice str = containsVowels 3 str && doubleLetter str && noDisallowed str
### Pair of letters that appears twice
Use a similar approach to `doubleLetter` above, but instead of comparing the two chars for equality, pack them into a string and check if they are a substring of the rest of the string.
Use a similar approach to `doubleLetter` above, but instead of comparing the two
chars for equality, pack them into a string and check if they are a substring of
the rest of the string.
```idris
pairTwice : String -> Bool
@ -122,7 +159,9 @@ pairTwice str with (asList str)
### Letter that repeats after one letter
More of the same, but this one gets extra fun because we are matching 3 letters at a time. This would be a lot nicer if we could get away without nesting the `with` blocks, but due to the aformentioned compiler bug, we need the nesting.
More of the same, but this one gets extra fun because we are matching 3 letters
at a time. This would be a lot nicer if we could get away without nesting the
`with` blocks, but due to the aformentioned compiler bug, we need the nesting.
```idris
letterRepeatGap : String -> Bool
@ -151,7 +190,8 @@ isNice' str = pairTwice str && letterRepeatGap str
### Part 1
Split our input up into a list of its component strings, and then count how many satisfy the "nice" criteria.
Split our input up into a list of its component strings, and then count how many
satisfy the "nice" criteria.
```idris
part1 : Eff (PartEff String) (Nat, ())

View file

@ -27,7 +27,9 @@ import Data.IORef
### Grid structure
Since this is our first 2d grid problem, we'll keep it simple and use a `Vect` of `Vect`s to store our problem state, we'll revisit a more complicated but more efficient structure for storing a 2d `Grid` in a later problem.
Since this is our first 2d grid problem, we'll keep it simple and use a `Vect`
of `Vect`s to store our problem state, we'll revisit a more complicated but more
efficient structure for storing a 2d `Grid` in a later problem.
This alias adds 1 to each of its arguments to ensure non-emptyness.
@ -36,7 +38,8 @@ Grid : (rows, cols : Nat) -> Type -> Type
Grid rows cols e = Vect (S rows) (Vect (S cols) e)
```
We also provide a type alias for the coordinates in this grid, a simple pair of `Fin`s.
We also provide a type alias for the coordinates in this grid, a simple pair of
`Fin`s.
```idris
Coord : (rows, cols : Nat) -> Type
@ -93,7 +96,9 @@ Show (Range rows cols) where
Helper function to extract a range of values from our Grid.
First extracts the range of rows this `Range` touches, then maps a an extractor for the range of columns it touches across them, before lazily concatenating the resulting lists.
First extracts the range of rows this `Range` touches, then maps a an extractor
for the range of columns it touches across them, before lazily concatenating the
resulting lists.
```idris
extractRange : Range rows cols -> Grid rows cols e -> LazyList e
@ -118,7 +123,7 @@ Show (Command rows cols) where
"{\{show action}: \{show range}}"
-->
### Parsing
### Parsing
Pattern match on the action string, throwing an error if the action was invalid
@ -131,7 +136,8 @@ parseAction "toggle" = pure Toggle
parseAction str = throw "Invalid action: \{str}"
```
Attempt to split the string into two parts on the comma, and then attempt to parse the parts as `Fin`s, throwing an appropriate error if anything goes wrong
Attempt to split the string into two parts on the comma, and then attempt to
parse the parts as `Fin`s, throwing an appropriate error if anything goes wrong
```idris
parseCoord : Has (Except String) fs =>
@ -157,7 +163,8 @@ parseRange pair1 pair2 = do
pure $ MkRange top_left bottom_right
```
Split a string into a list of parts, pattern matching those parts to attempt to extract a `Command`.
Split a string into a list of parts, pattern matching those parts to attempt to
extract a `Command`.
```idris
parseCommand : Has (Except String) fs => Has Logger fs =>
@ -181,7 +188,10 @@ parseCommand input = do
## Problem structure
Since we are dealing with a million slots here, we'll want some level of true mutability. The actual structure containing the slots doesn't need to be modified once its setup, so we'll make the mutability interior to the slots and keep a `Grid` of `IORef`s.
Since we are dealing with a million slots here, we'll want some level of true
mutability. The actual structure containing the slots doesn't need to be
modified once its setup, so we'll make the mutability interior to the slots and
keep a `Grid` of `IORef`s.
We'll setup a helper function to initialize our grid based on a seed value.
@ -193,7 +203,8 @@ ioGrid rows cols seed =
in traverse (traverse id) grid
```
Convert a `Grid` of `IORef`s into a `Grid` of pure values by traversing the `readIORef` operation over our `Grid`.
Convert a `Grid` of `IORef`s into a `Grid` of pure values by traversing the
`readIORef` operation over our `Grid`.
```idris
purify : Has IO fs =>
@ -208,9 +219,12 @@ purify grid = traverse (traverse readIORef) grid
```idris
namespace Part1
```
Apply a given command to our `Grid` of `IORef`s.
Use our `extractRange` function to extract all the `IORef`s in the grid cells touched by our `Range` and then traverse an appropriate mutating action over them.
Apply a given command to our `Grid` of `IORef`s.
Use our `extractRange` function to extract all the `IORef`s in the grid cells
touched by our `Range` and then traverse an appropriate mutating action over
them.
```idris
applyCommand : Has IO fs =>
@ -223,7 +237,8 @@ Use our `extractRange` function to extract all the `IORef`s in the grid cells to
Toggle => Lazy.traverse_ (`modifyIORef` not) cells
```
Apply a list of commands to our `Grid` of `IORef`s, doing some debug logging along the way.
Apply a list of commands to our `Grid` of `IORef`s, doing some debug logging
along the way.
```idris
export
@ -246,7 +261,8 @@ Apply a list of commands to our `Grid` of `IORef`s, doing some debug logging alo
namespace Part2
```
Much the same as above, but instead we apply the part 2 rules to a `Grid` of `Nat`.
Much the same as above, but instead we apply the part 2 rules to a `Grid` of
`Nat`.
```idris
applyCommand : Has IO fs =>
@ -259,7 +275,9 @@ Much the same as above, but instead we apply the part 2 rules to a `Grid` of `Na
Toggle => Lazy.traverse_ (`modifyIORef` (+ 2)) cells
```
Identical to above, except for using our part 2 `applyCommand`. We can use the same name here because we have the two variants behind namespaces and Idris can disambiguate via the types.
Identical to above, except for using our part 2 `applyCommand`. We can use the
same name here because we have the two variants behind namespaces and Idris can
disambiguate via the types.
```idris
export
@ -280,7 +298,9 @@ Identical to above, except for using our part 2 `applyCommand`. We can use the s
### Part 1
Parse our commands, generate our initial `Grid` with all the lights turned off (represented by `False`), apply our commands to it, purify it, and count the lights that are turned on.
Parse our commands, generate our initial `Grid` with all the lights turned off
(represented by `False`), apply our commands to it, purify it, and count the
lights that are turned on.
Pass out our list of parsed commands as the context for reuse in part 2.
@ -298,7 +318,9 @@ part1 = do
### Part 2
This time, use an initial `Grid` with all brightness values at 0, apply our list of preparsed commands using our part 2 `applyCommands` function (selected via the type signature), and then add up the brightnesses.
This time, use an initial `Grid` with all brightness values at 0, apply our list
of preparsed commands using our part 2 `applyCommands` function (selected via
the type signature), and then add up the brightnesses.
```idris
part2 : List (Command 999 999) -> Eff (PartEff String) Nat

View file

@ -2,9 +2,16 @@
This day provides us a gentle introduction to dependent maps.
Per the problem specification, each wire can only be output to by one gate. To encode this property in the type, we'll tag `Gate` with the output wire in the type, and then store our ciruct as a dependent map from wires to `Gate`s. This ensures that the circut only contains one gate outputting to each wire, and that looking up a wire in the ciruct produces exactly the gate that outputs to it through type checking.
Per the problem specification, each wire can only be output to by one gate. To
encode this property in the type, we'll tag `Gate` with the output wire in the
type, and then store our ciruct as a dependent map from wires to `Gate`s. This
ensures that the circut only contains one gate outputting to each wire, and that
looking up a wire in the ciruct produces exactly the gate that outputs to it
through type checking.
Ensuring that the input contains only one gate outputting for each wire is done through throwing a runtime error in the parsing function if a second gate outputting to a given wire is found in the input.
Ensuring that the input contains only one gate outputting for each wire is done
through throwing a runtime error in the parsing function if a second gate
outputting to a given wire is found in the input.
<!-- idris
module Years.Y2015.Day7
@ -32,7 +39,8 @@ import Decidable.Equality
### Problem structure
Define type aliases for wires and literals, and specify that an input can be either a literal or a wire.
Define type aliases for wires and literals, and specify that an input can be
either a literal or a wire.
```idris
Wire : Type
@ -128,9 +136,16 @@ parseGate str = do
_ => throw "Invalid Gate: \{str}"
```
Break the input into lines, traverse `parseGate` over the lines, and collect the results into our circut DMap.
Break the input into lines, traverse `parseGate` over the lines, and collect the
results into our circut DMap.
The type of a value in a `SortedDMap` depends on the value of the key that refers to it, in the type constructor `SortedDMap k v`, `v` is of type `k -> Type`, this is the function the map calls to calculate the type of the value from the value of the key. This, not so coincidentally, is the type signature of our `Gate` type constructor, which we can slot in here to get a map from a wire to the gate outputting to that wire, preventing us from accidentally associating a wire to the wrong gate.
The type of a value in a `SortedDMap` depends on the value of the key that
refers to it, in the type constructor `SortedDMap k v`, `v` is of type
`k -> Type`, this is the function the map calls to calculate the type of the
value from the value of the key. This, not so coincidentally, is the type
signature of our `Gate` type constructor, which we can slot in here to get a map
from a wire to the gate outputting to that wire, preventing us from accidentally
associating a wire to the wrong gate.
```idris
parseGates : Has (Except String) fs => Has Logger fs =>
@ -150,7 +165,8 @@ parseGates input = do
## Solver Functions
Recursively solve for the value on a wire, keeping a cache of already solved wires
Recursively solve for the value on a wire, keeping a cache of already solved
wires
```idris
covering
@ -197,7 +213,10 @@ solveWire wire = do
### Part 1
Parse the input, then feed it and an initial empty cache into our `solveWire` function, passing the produced value as the output and the circut, represented as a dependent map from wires to gates, as well as the output signal from wire 'a' as the context for part 2.
Parse the input, then feed it and an initial empty cache into our `solveWire`
function, passing the produced value as the output and the circut, represented
as a dependent map from wires to gates, as well as the output signal from wire
'a' as the context for part 2.
```idris
covering
@ -214,7 +233,8 @@ part1 = do
### Part 2
Override the value for the 'b' wire to the output from the 'a' wire in part 1, then rerun our calcuation to find the new output for the 'a' wire.
Override the value for the 'b' wire to the output from the 'a' wire in part 1,
then rerun our calcuation to find the new output for the 'a' wire.
```idris
covering