Compare commits
4 commits
472dc28c3b
...
5a313c952f
Author | SHA1 | Date | |
---|---|---|---|
5a313c952f | |||
fc7f79a96a | |||
080b9f8c77 | |||
feb75002bd |
9 changed files with 255 additions and 6 deletions
|
@ -7,6 +7,11 @@ The goal of this project is to get all 500 currently available stars in the form
|
||||||
- [Runner](src/Runner.md)
|
- [Runner](src/Runner.md)
|
||||||
|
|
||||||
Provides data structures for structuring the division of the project into years, days, and parts.
|
Provides data structures for structuring the division of the project into years, days, and parts.
|
||||||
|
|
||||||
|
- [Main](src/Main.md)
|
||||||
|
|
||||||
|
Provides the `Runner` based command line interface for running a selected day's solution.
|
||||||
|
|
||||||
- [Util](src/Util.md)
|
- [Util](src/Util.md)
|
||||||
|
|
||||||
Provides extensions of the functionality of the standard library and external libraries. Extensions to the standard library are in the base of this module.
|
Provides extensions of the functionality of the standard library and external libraries. Extensions to the standard library are in the base of this module.
|
||||||
|
@ -24,3 +29,4 @@ The goal of this project is to get all 500 currently available stars in the form
|
||||||
- [Day 4](src/Years/Y2015/Day4.md)
|
- [Day 4](src/Years/Y2015/Day4.md)
|
||||||
- [Day 5](src/Years/Y2015/Day5.md)
|
- [Day 5](src/Years/Y2015/Day5.md)
|
||||||
- [Day 6](src/Years/Y2015/Day6.md)
|
- [Day 6](src/Years/Y2015/Day6.md)
|
||||||
|
- [Day 7](src/Years/Y2015/Day7.md)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import Years.Y2015.Day3
|
||||||
import Years.Y2015.Day4
|
import Years.Y2015.Day4
|
||||||
import Years.Y2015.Day5
|
import Years.Y2015.Day5
|
||||||
import Years.Y2015.Day6
|
import Years.Y2015.Day6
|
||||||
|
import Years.Y2015.Day7
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Days
|
# Days
|
||||||
|
@ -58,6 +59,12 @@ y2015 = MkYear 2015 [
|
||||||
, day6
|
, day6
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## [Day 7](Y2015/Day7.md)
|
||||||
|
|
||||||
|
```idris
|
||||||
|
, day7
|
||||||
|
```
|
||||||
|
|
||||||
```idris
|
```idris
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Day 1
|
# 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.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Day 2
|
# Year 2015 Day 2
|
||||||
|
|
||||||
This day provides us our first little taste of effectful parsing
|
This day provides us our first little taste of effectful parsing
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Day 3
|
# 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.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Day 4
|
# 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.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Day 5
|
# 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.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Day 6
|
# Year 2015 Day 6
|
||||||
|
|
||||||
<!-- idris
|
<!-- idris
|
||||||
module Years.Y2015.Day6
|
module Years.Y2015.Day6
|
||||||
|
|
236
src/Years/Y2015/Day7.md
Normal file
236
src/Years/Y2015/Day7.md
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
# Year 2015 Day 7
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
import Control.Eff
|
||||||
|
|
||||||
|
import Runner
|
||||||
|
-->
|
||||||
|
|
||||||
|
```idris
|
||||||
|
import Data.Bits
|
||||||
|
import Data.Fin
|
||||||
|
import Data.String
|
||||||
|
import Data.List1
|
||||||
|
import Data.SortedMap
|
||||||
|
import Data.SortedMap.Dependent
|
||||||
|
import Decidable.Equality
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- idris
|
||||||
|
%default total
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Parsing and data structures
|
||||||
|
|
||||||
|
### Problem structure
|
||||||
|
|
||||||
|
Define type aliases for wires and literals, and specify that an input can be either a literal or a wire.
|
||||||
|
|
||||||
|
```idris
|
||||||
|
Wire : Type
|
||||||
|
Wire = String
|
||||||
|
|
||||||
|
Literal : Type
|
||||||
|
Literal = Bits16
|
||||||
|
|
||||||
|
Input : Type
|
||||||
|
Input = Either Literal Wire
|
||||||
|
```
|
||||||
|
|
||||||
|
Description of a Gate, tagged in the type with the name of the output wire
|
||||||
|
|
||||||
|
```idris
|
||||||
|
data Gate : Wire -> Type where
|
||||||
|
Constant : (x : Input) -> {output : Wire} -> Gate output
|
||||||
|
And : (x, y : Input) -> {output : Wire} -> Gate output
|
||||||
|
Or : (x, y : Input) -> {output : Wire} -> Gate output
|
||||||
|
Not : (x : Input) -> {output : Wire} -> Gate output
|
||||||
|
LShift : (x : Input) -> (y : Index {a = Literal}) -> {output : Wire}
|
||||||
|
-> Gate output
|
||||||
|
RShift : (x : Input) -> (y : Index {a = Literal}) -> {output : Wire}
|
||||||
|
-> Gate output
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parsing functions
|
||||||
|
|
||||||
|
Parse a shift value, making sure its bounded properly to index a `Bits16`
|
||||||
|
|
||||||
|
```idris
|
||||||
|
parseShift : Has (Except String) fs => String -> Eff fs (Index {a = Literal})
|
||||||
|
parseShift str =
|
||||||
|
note "Invalid shift: \{str}" $ parsePositive str
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure that this string consists only of lowercase letters
|
||||||
|
|
||||||
|
```idris
|
||||||
|
parseWire : Has (Except String) fs => String -> Eff fs Wire
|
||||||
|
parseWire str =
|
||||||
|
if all isLower (unpack str)
|
||||||
|
then pure str
|
||||||
|
else throw "Invalid wire \{str}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse either a wire or a literal
|
||||||
|
|
||||||
|
```idris
|
||||||
|
parseInput : Has (Except String) fs => String -> Eff fs Input
|
||||||
|
parseInput str =
|
||||||
|
case parsePositive str of
|
||||||
|
Nothing => map Right . parseWire $ str
|
||||||
|
Just x => pure $ Left x
|
||||||
|
```
|
||||||
|
|
||||||
|
Split the string into components and pattern match on them to extract a gate
|
||||||
|
|
||||||
|
```idris
|
||||||
|
parseGate : Has (Except String) fs => Has Logger fs =>
|
||||||
|
String -> Eff fs (wire : Wire ** Gate wire)
|
||||||
|
parseGate str = do
|
||||||
|
debug "Parsing gate: \{str}"
|
||||||
|
case split (== ' ') str of
|
||||||
|
x ::: ["->", output ] => do
|
||||||
|
x <- parseInput x
|
||||||
|
output <- parseWire output
|
||||||
|
pure (output ** Constant x)
|
||||||
|
"NOT" ::: [x, "->", output] => do
|
||||||
|
x <- parseInput x
|
||||||
|
output <- parseWire output
|
||||||
|
pure (output ** Not x)
|
||||||
|
x ::: ["AND", y, "->", output] => do
|
||||||
|
x <- parseInput x
|
||||||
|
y <- parseInput y
|
||||||
|
output <- parseWire output
|
||||||
|
pure (output ** And x y)
|
||||||
|
x ::: ["OR", y, "->", output] => do
|
||||||
|
x <- parseInput x
|
||||||
|
y <- parseInput y
|
||||||
|
output <- parseWire output
|
||||||
|
pure (output ** Or x y)
|
||||||
|
x ::: ["RSHIFT", y, "->", output] => do
|
||||||
|
x <- parseInput x
|
||||||
|
y <- parseShift y
|
||||||
|
output <- parseWire output
|
||||||
|
pure (output ** RShift x y)
|
||||||
|
x ::: ["LSHIFT", y, "->", output] => do
|
||||||
|
x <- parseInput x
|
||||||
|
y <- parseShift y
|
||||||
|
output <- parseWire output
|
||||||
|
pure (output ** LShift x y)
|
||||||
|
_ => throw "Invalid Gate: \{str}"
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```idris
|
||||||
|
parseGates : Has (Except String) fs => Has Logger fs =>
|
||||||
|
String -> Eff fs (SortedDMap Wire Gate)
|
||||||
|
parseGates input = do
|
||||||
|
gates : List (wire : Wire ** Gate wire) <-
|
||||||
|
traverse parseGate . lines . trim $ input
|
||||||
|
foldlM insertGate empty gates
|
||||||
|
where
|
||||||
|
insertGate : SortedDMap Wire Gate -> (wire : Wire ** Gate wire)
|
||||||
|
-> Eff fs $ SortedDMap Wire Gate
|
||||||
|
insertGate map (wire ** gate) =
|
||||||
|
if isJust $ lookup wire map
|
||||||
|
then throw "Duplicate gate for \{wire}"
|
||||||
|
else pure $ insert wire gate map
|
||||||
|
```
|
||||||
|
|
||||||
|
## Solver Functions
|
||||||
|
|
||||||
|
Recursively solve for the value on a wire, keeping a cache of already solved wires
|
||||||
|
|
||||||
|
```idris
|
||||||
|
covering
|
||||||
|
solveWire : Has (Except String) fs => Has (State (SortedMap Wire Literal)) fs =>
|
||||||
|
Has (Reader (SortedDMap Wire Gate)) fs =>
|
||||||
|
(wire : Wire) -> Eff fs Literal
|
||||||
|
solveWire wire = do
|
||||||
|
-- Short circut if we are already in the cache
|
||||||
|
Nothing <- map (lookup wire) get
|
||||||
|
| Just cached => pure cached
|
||||||
|
-- Find the gate that outputs to this wire in our circut
|
||||||
|
Just gate <- map (lookupPrecise wire) ask
|
||||||
|
| _ => throw "No output for wire \{wire}"
|
||||||
|
-- Recursively solve for the inputs and apply this gate's operation
|
||||||
|
res : Literal <- case gate of
|
||||||
|
Constant x => fromInput x
|
||||||
|
And x y => do
|
||||||
|
x <- fromInput x
|
||||||
|
y <- fromInput y
|
||||||
|
pure $ x .&. y
|
||||||
|
Or x y => do
|
||||||
|
x <- fromInput x
|
||||||
|
y <- fromInput y
|
||||||
|
pure $ x .|. y
|
||||||
|
Not x => do
|
||||||
|
x <- fromInput x
|
||||||
|
pure $ complement x
|
||||||
|
LShift x y => do
|
||||||
|
x <- fromInput x
|
||||||
|
pure $ x `shiftL` y
|
||||||
|
RShift x y => do
|
||||||
|
x <- fromInput x
|
||||||
|
pure $ x `shiftR` y
|
||||||
|
-- Add this gate's output to the cache
|
||||||
|
modify $ insert wire res
|
||||||
|
pure res
|
||||||
|
where
|
||||||
|
fromInput : (i : Input) -> Eff fs Literal
|
||||||
|
fromInput (Left x) = pure x
|
||||||
|
fromInput (Right x) = solveWire x
|
||||||
|
```
|
||||||
|
|
||||||
|
## Part Functions
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
```idris
|
||||||
|
covering
|
||||||
|
part1 : Eff (PartEff String) (Bits16, (SortedDMap Wire Gate, Literal))
|
||||||
|
part1 = do
|
||||||
|
circut <- askAt "input" >>= parseGates
|
||||||
|
(value, _) <-
|
||||||
|
runState empty . runReader circut $ solveWire "a"
|
||||||
|
{fs = State (SortedMap Wire Literal)
|
||||||
|
:: Reader (SortedDMap Wire Gate)
|
||||||
|
:: PartEff String }
|
||||||
|
pure (value, (circut, value))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
```idris
|
||||||
|
covering
|
||||||
|
part2 : (SortedDMap Wire Gate, Literal) -> Eff (PartEff String) Bits16
|
||||||
|
part2 (circut, value) = do
|
||||||
|
let circut = insert "b" (Constant (Left value)) circut
|
||||||
|
(value, _) <-
|
||||||
|
runState empty . runReader circut $ solveWire "a"
|
||||||
|
{fs = State (SortedMap Wire Literal)
|
||||||
|
:: Reader (SortedDMap Wire Gate)
|
||||||
|
:: PartEff String }
|
||||||
|
pure value
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- idris
|
||||||
|
public export covering
|
||||||
|
day7 : Day
|
||||||
|
day7 = Both 7 part1 part2
|
||||||
|
-->
|
Loading…
Add table
Reference in a new issue