Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit df1048b

Browse files
committed
day 20 reflections
1 parent d05e469 commit df1048b

File tree

2 files changed

+128
-20
lines changed

2 files changed

+128
-20
lines changed

‎reflections.md

Lines changed: 125 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,32 +1950,142 @@ Day 20
19501950

19511951
[d20c]: https://github.com/mstksg/advent-of-code-2017/blob/master/src/AOC2017/Day20.hs
19521952

1953+
Day 20 starts out as a simple physics simulator/numerical integrator:
1954+
19531955
```haskell
1954-
data S = S { _sPos :: !(V.Vector (Maybe (L.V3 Int)))
1955-
, _sVel :: !(V.Vector (Maybe (L.V3 Int)))
1956-
, _sAcc :: !(V.Vector (Maybe (L.V3 Int)))
1957-
}
1958-
deriving Show
1956+
type Point = L.V3 Int
1957+
1958+
data Particle a = P { _pAcc :: !a
1959+
, _pVel :: !a
1960+
, _pPos :: !a
1961+
}
1962+
deriving (Functor, Foldable, Traversable, Show, Eq, Ord)
1963+
1964+
type System = [Particle Point]
1965+
```
1966+
1967+
Using the *[linear][]* package again, for `V3`, a 3-vector. It's also
1968+
convenient to decide a `Particle` to contain a description of its acceleration,
1969+
velocity, and position. Our whole system will be a list of `Particle Point`s.
1970+
Note that we parameterize `Particle` so that we can give useful higher-kinded
1971+
instances like `Functor` and `Traversable`.
1972+
1973+
Stepping the simulation ends up being just stepping every particle.
1974+
Interestingly enough, we can actually use `scanl (+) 0` (for `Traversable`) to
1975+
do the integration step:
1976+
1977+
```haskell
1978+
-- | scanl generalized to work on all Traversable
1979+
scanlT :: Traversable t => (b -> a -> b) -> b -> t a -> t b
1980+
scanlT = -- implementatation left as exercise, but I really wish this was
1981+
-- already in base :|
1982+
1983+
step :: Num a => Particle a -> Particle a
1984+
step = scanlT (+) 0
1985+
```
1986+
1987+
This is because it replaces `_pAcc` with `0 + _pAcc`, and then it replaces
1988+
`_pVel` with `0 + _pAcc + _pVel`, and then finally replaces `_pPos` with `0 +
1989+
_pAcc + _pVel + _pPos` -- just like the problem asks!
1990+
1991+
For part 1, we can just `map step` a `System` several points, and then find
1992+
closest point:
1993+
1994+
```haskell
1995+
norm :: Point -> Int
1996+
norm = sum . fmap abs
1997+
1998+
day20a :: System -> Int
1999+
day20a = V.minIndex . V.fromList -- hijacking minIndex from Vector
2000+
. map (norm . _pPos)
2001+
. (!! 1000)
2002+
. iterate (map step)
2003+
```
2004+
2005+
However, we could also be sneaky and just find the "maximum" normed initial
2006+
vector, which is correct in the case where all of our initial accelerations are
2007+
differently normed, and sometimes correct in the case where we have duplicated
2008+
accelerations.
2009+
2010+
```haskell
2011+
day20a :: System -> Int
2012+
day20a = V.minIndex . V.fromList
2013+
. (map . fmap) norm
2014+
. parse
2015+
```
2016+
2017+
For part 2, we can define a function that takes out all "duplicated" points,
2018+
using a frequency map and filtering for frequencies greater than 1:
2019+
2020+
```haskell
2021+
collide :: System -> System
2022+
collide s0 = filter ((`S.notMember` collisions) . _pPos) s0
2023+
where
2024+
collisions :: S.Set Point
2025+
collisions = M.keysSet . M.filter @Int (> 1)
2026+
. M.fromListWith (+)
2027+
. map ((,1) . _pPos)
2028+
$ toList s0
2029+
```
2030+
2031+
Now we just iterate `collide . map step`.
2032+
2033+
We can pick the thousandth element again, like we might have for part 1.
2034+
However, we can be a little smart with a stopping condition:
2035+
2036+
```haskell
2037+
day20b :: Challenge
2038+
day20b = show . length . fromJust . find stop
2039+
. iterate (collide . map step)
2040+
. parse
2041+
where
2042+
stop = (> 1000) . minimum . map (norm . _sPos)
2043+
```
2044+
2045+
Here, we iterate until the particle *closest* to the origin is greater than
2046+
a 1000-cube away from the origin. Essentially, this is waiting until all of
2047+
the points clear a 2000-wide cube around the origin. Thinking about the input,
2048+
there will be some particles that start out near the origin and start heading
2049+
*towards* the origin. This condition will wait until the last of those
2050+
particles exits the origin cube, and check for the number of collisions then.
2051+
2052+
### Parsing
2053+
2054+
We can parse into `System` using really silly view patterns :)
2055+
2056+
```haskell
2057+
parse :: String -> System
2058+
parse = map parseLine . lines
2059+
where
2060+
parseLine :: String -> Particle Point
2061+
parseLine (map(read.filter numChar).splitOn","->[pX,pY,pZ,vX,vY,vZ,aX,aY,aZ])
2062+
= P { _pAcc = L.V3 aX aY aZ
2063+
, _pVel = L.V3 vX vY vZ
2064+
, _pPos = L.V3 pX pY pZ
2065+
}
2066+
parseLine _ = error "No parse"
2067+
numChar :: Char -> Bool
2068+
numChar c = isDigit c || c == '-'
19592069
```
19602070

19612071
### Day 20 Benchmarks
19622072

19632073
```
19642074
>> Day 20a
19652075
benchmarking...
1966-
time 33.64 ms (30.59 ms .. 37.78 ms)
1967-
0.957 R2 (0.920 R2 .. 0.998 R2)
1968-
mean 33.70 ms (32.37 ms .. 36.27 ms)
1969-
std dev 3.692 ms (1.222 ms .. 5.172 ms)
1970-
variance introduced by outliers: 42% (moderately inflated)
2076+
time 29.87 ms (28.16 ms .. 32.18 ms)
2077+
0.989 R2 (0.979 R2 .. 0.997 R2)
2078+
mean 33.94 ms (32.33 ms .. 38.26 ms)
2079+
std dev 5.085 ms (1.678 ms .. 8.683 ms)
2080+
variance introduced by outliers: 61% (severely inflated)
19712081
19722082
>> Day 20b
19732083
benchmarking...
1974-
time 74.06 ms (68.03 ms .. 84.42 ms)
1975-
0.964 R2 (0.879 R2 .. 0.997 R2)
1976-
mean 76.05 ms (72.32 ms .. 83.21 ms)
1977-
std dev 7.736 ms (4.126 ms .. 11.56 ms)
1978-
variance introduced by outliers: 29% (moderately inflated)
2084+
time 67.18 ms (64.33 ms .. 72.99 ms)
2085+
0.990 R2 (0.975 R2 .. 0.998 R2)
2086+
mean 66.90 ms (64.83 ms .. 68.67 ms)
2087+
std dev 3.437 ms (2.439 ms .. 4.727 ms)
2088+
variance introduced by outliers: 16% (moderately inflated)
19792089
```
19802090

19812091
Day 21

‎src/AOC2017/Day20.hs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ day20a = show . V.minIndex . V.fromList
4545

4646
day20b :: Challenge
4747
day20b = show . length . fromJust . find stop
48-
. (map . map) (norm . _pPos)
4948
. iterate (collide . map step)
5049
. parse
5150
where
52-
stop = (> 1000) . minimum
51+
stop = (> 1000) . minimum.map (norm . _pPos)
5352

5453
parse :: String -> System
5554
parse = map parseLine . lines
@@ -61,6 +60,5 @@ parse = map parseLine . lines
6160
, _pPos = L.V3 pX pY pZ
6261
}
6362
parseLine _ = error "No parse"
64-
65-
numChar :: Char -> Bool
66-
numChar c = isDigit c || c == '-'
63+
numChar :: Char -> Bool
64+
numChar c = isDigit c || c == '-'

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /