Skip to content

Commit

Permalink
Solve Day 13 (#23)
Browse files Browse the repository at this point in the history
* Setup Day 13

* Solve part 1

* update readme to include part 2

* Almost solve part 2

* Solve part 2

* cleanup solution

* Use CRT to solve part 2
  • Loading branch information
manuphatak committed Dec 20, 2020
1 parent 9c1227b commit fda91f2
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 1 deletion.
4 changes: 3 additions & 1 deletion AdventOfCode2020.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: c776df2c7388f0018c9a5988ce515debb8e3d8c2349f47541399322c6a62ae17
-- hash: c5acf21b2e0800a49d88206737c872aeb4f5e7729e34b2df57f8c4ac2af8b30b

name: AdventOfCode2020
version: 2.0.2.0
Expand Down Expand Up @@ -42,6 +42,7 @@ library
Day09.Solution
Day09.Utils
Day11.Solution
Day13.Solution
Practice.Foldable
Template.Solution
other-modules:
Expand Down Expand Up @@ -77,6 +78,7 @@ test-suite AdventOfCode2020-test
Day09.SolutionSpec
Day09.UtilsSpec
Day11.SolutionSpec
Day13.SolutionSpec
Practice.FoldableSpec
Template.SolutionSpec
Paths_AdventOfCode2020
Expand Down
126 changes: 126 additions & 0 deletions src/Day13/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
## Day 13: Shuttle Search

Your ferry can make it safely to a nearby port, but it won't get much further. When you call to book another ship, you discover that no ships embark from that port to your vacation island. You'll need to get from the port to the nearest airport.

Fortunately, a shuttle bus service is available to bring you from the sea port to the airport! Each bus has an ID number that also indicates _how often the bus leaves for the airport_ .

Bus schedules are defined based on a _timestamp_ that measures the _number of minutes_ since some fixed reference point in the past. At timestamp `0` , every bus simultaneously departed from the sea port. After that, each bus travels to the airport, then various other locations, and finally returns to the sea port to repeat its journey forever.

The time this loop takes a particular bus is also its ID number: the bus with ID `5` departs from the sea port at timestamps `0` , `5` , `10` , `15` , and so on. The bus with ID `11` departs at `0` , `11` , `22` , `33` , and so on. If you are there when the bus departs, you can ride that bus to the airport!

Your notes (your puzzle input) consist of two lines. The first line is your estimate of the _earliest timestamp you could depart on a bus_ . The second line lists the bus IDs that are in service according to the shuttle company; entries that show `x` must be out of service, so you decide to ignore them.

To save time once you arrive, your goal is to figure out _the earliest bus you can take to the airport_ . (There will be exactly one such bus.)

For example, suppose you have the following notes:

```
939
7,13,x,x,59,x,31,19
```

Here, the earliest timestamp you could depart is `939` , and the bus IDs in service are `7` , `13` , `59` , `31` , and `19` . Near timestamp `939` , these bus IDs depart at the times marked `D` :

```
time bus 7 bus 13 bus 59 bus 31 bus 19
929 . . . . .
930 . . . D .
931 D . . . D
932 . . . . .
933 . . . . .
934 . . . . .
935 . . . . .
936 . D . . .
937 . . . . .
938 D . . . .
939 . . . . .
940 . . . . .
941 . . . . .
942 . . . . .
943 . . . . .
944 . . D . .
945 D . . . .
946 . . . . .
947 . . . . .
948 . . . . .
949 . D . . .
```

The earliest bus you could take is bus ID `59` . It doesn't depart until timestamp `944` , so you would need to wait `944 - 939 = 5` minutes before it departs. Multiplying the bus ID by the number of minutes you'd need to wait gives _`295`_ .

_What is the ID of the earliest bus you can take to the airport multiplied by the number of minutes you'll need to wait for that bus?_

## Part Two

The shuttle company is running a contest : one gold coin for anyone that can find the earliest timestamp such that the first bus ID departs at that time and each subsequent listed bus ID departs at that subsequent minute. (The first line in your input is no longer relevant.)

For example, suppose you have the same list of bus IDs as above:

```
7,13,x,x,59,x,31,19
```

An `x` in the schedule means there are no constraints on what bus IDs must depart at that time.

This means you are looking for the earliest timestamp (called `t` ) such that:

- Bus ID `7` departs at timestamp `t` .
- Bus ID `13` departs one minute after timestamp `t` .
- There are no requirements or restrictions on departures at two or three minutes after timestamp `t` .
- Bus ID `59` departs four minutes after timestamp `t` .
- There are no requirements or restrictions on departures at five minutes after timestamp `t` .
- Bus ID `31` departs six minutes after timestamp `t` .
- Bus ID `19` departs seven minutes after timestamp `t` .

The only bus departures that matter are the listed bus IDs at their specific offsets from `t` . Those bus IDs can depart at other times, and other bus IDs can depart at those times. For example, in the list above, because bus ID `19` must depart seven minutes after the timestamp at which bus ID `7` departs, bus ID `7` will always _also_ be departing with bus ID `19` at seven minutes after timestamp `t` .

In this example, the earliest timestamp at which this occurs is _`1068781`_ :

```
time bus 7 bus 13 bus 59 bus 31 bus 19
1068773 . . . . .
1068774 D . . . .
1068775 . . . . .
1068776 . . . . .
1068777 . . . . .
1068778 . . . . .
1068779 . . . . .
1068780 . . . . .
1068781 D . . . .
1068782 . D . . .
1068783 . . . . .
1068784 . . . . .
1068785 . . D . .
1068786 . . . . .
1068787 . . . D .
1068788 D . . . D
1068789 . . . . .
1068790 . . . . .
1068791 . . . . .
1068792 . . . . .
1068793 . . . . .
1068794 . . . . .
1068795 D D . . .
1068796 . . . . .
1068797 . . . . .
```

In the above example, bus ID `7` departs at timestamp `1068788` (seven minutes after `t` ). This is fine; the only requirement on that minute is that bus ID `19` departs then, and it does.

Here are some other examples:

- The earliest timestamp that matches the list `17,x,13,19` is _`3417`_ .
- `67,7,59,61` first occurs at timestamp _`754018`_ .
- `67,x,7,59,61` first occurs at timestamp _`779210`_ .
- `67,7,x,59,61` first occurs at timestamp _`1261476`_ .
- `1789,37,47,1889` first occurs at timestamp _`1202161486`_ .

However, with so many bus IDs in your list, surely the actual earliest timestamp will be larger than `100000000000000` !

_What is the earliest timestamp such that all of the listed bus IDs depart at offsets matching their positions in the list?_

## Link

[https://adventofcode.com/2020/day/13][1]

[1]: https://adventofcode.com/2020/day/13
11 changes: 11 additions & 0 deletions src/Day13/SOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Day 13 Solution

[WolframAlpha Link](https://www.wolframalpha.com/input/?i=%28x+%2B+0%29+mod+23%2C+%28x+%2B+17%29+mod+37%2C+%28x+%2B+23%29+mod+863%2C+%28x+%2B+35%29+mod+19%2C+%28x+%2B+36%29+mod+13%2C+%28x+%2B+40%29+mod+17%2C+%28x+%2B+52%29+mod+29%2C+%28x+%2B+54%29+mod+571%2C+%28x+%2B+95%29+mod+41)

## Code

![Code](./img/day_13_code.png)

## Solution

![Solution](./img/day_13_solution.png)
74 changes: 74 additions & 0 deletions src/Day13/Solution.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module Day13.Solution
( busAlignment,
crt,
earliestBus,
parseBusIds,
parseSchedule,
part1,
part2,
)
where

import Advent.Utils (fromRightOrShowError)
import Data.Function (on)
import Data.List (minimumBy)
import Data.Maybe (catMaybes)
import Text.Parsec

part1 :: String -> String
part1 = show . uncurry (*) . earliestBus . fromRightOrShowError . parseSchedule

part2 :: String -> String
part2 = show . busAlignment . snd . fromRightOrShowError . parseSchedule

parseSchedule :: String -> Either ParseError (Integer, [Maybe Integer])
parseSchedule = parse scheduleParser ""
where
scheduleParser :: Parsec String () (Integer, [Maybe Integer])
scheduleParser = (,) <$> (intParser <* endOfLine) <*> busIdsParser

parseBusIds :: String -> Either ParseError [Maybe Integer]
parseBusIds = parse busIdsParser ""

busIdsParser :: Parsec String () [Maybe Integer]
busIdsParser = busIdParser `sepBy1` char ','
where
busIdParser :: Parsec String () (Maybe Integer)
busIdParser = choice [Nothing <$ try (char 'x'), Just <$> intParser]

intParser :: Parsec String () Integer
intParser = read <$> many digit

earliestBus :: (Integer, [Maybe Integer]) -> (Integer, Integer)
earliestBus (start, busIds) = minimumBy (compare `on` fst) . map nextDeparture . catMaybes $ busIds
where
nextDeparture :: Integer -> (Integer, Integer)
nextDeparture bus = (bus - (start `mod` bus), bus)

busAlignment :: [Maybe Integer] -> Integer
busAlignment = uncurry subtract . crt . enumeratedBusIds
where
enumeratedBusIds :: [Maybe Integer] -> [(Integer, Integer)]
enumeratedBusIds xs = [(a, b) | (a, Just b) <- zip [0 ..] xs]

-- Chinese Remainder Theorem
-- https://stackoverflow.com/questions/35529211/chinese-remainder-theorem-haskell
crt :: (Integral a, Foldable t) => t (a, a) -> (a, a)
crt = foldr go (0, 1)
where
go :: Integral a => (a, a) -> (a, a) -> (a, a)
go (r1, m1) (r2, m2) = (r `mod` m, m)
where
r = r2 + m2 * (r1 - r2) * (m2 `inv` m1)
m = m2 * m1

-- Modular Inverse
inv :: Integral a => a -> a -> a
a `inv` m = let (_, i, _) = gcd' a m in i `mod` m

-- Extended Euclidean Algorithm
gcd' :: Integral c => c -> c -> (c, c, c)
gcd' 0 b = (b, 0, 1)
gcd' a b = (g, t - (b `div` a) * s, s)
where
(g, s, t) = gcd' (b `mod` a) a
Binary file added src/Day13/img/day_13_code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/Day13/img/day_13_solution.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions test/Day13/SolutionSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module Day13.SolutionSpec (spec) where

import Data.Foldable (for_)
import Day13.Solution
( busAlignment,
crt,
earliestBus,
parseBusIds,
parseSchedule,
part1,
part2,
)
import Test.Hspec

spec :: Spec
spec = parallel $ do
it "solves Part 1" $ do
input <- readFile "./test/Day13/input.txt"
part1 input `shouldBe` "119"
it "solves Part 2" $ do
input <- readFile "./test/Day13/input.txt"
part2 input `shouldBe` "1106724616194525"

let exampleSchedule = (939, [Just 7, Just 13, Nothing, Nothing, Just 59, Nothing, Just 31, Just 19])
describe "parseSchedule" $ do
it "is the schedule" $ do
input <- readFile "./test/Day13/example.txt"
parseSchedule input `shouldBe` Right exampleSchedule

describe "earliestBus" $ do
it "is" $ do
earliestBus exampleSchedule `shouldBe` (5, 59)

describe "busAlignment" $ do
let cases :: [(String, Integer)]
cases =
[ ("7,13,x,x,59,x,31,19", 1068781),
("17,x,13,19", 3417),
("67,7,59,61", 754018),
("67,x,7,59,61", 779210),
("67,7,x,59,61", 1261476),
("1789,37,47,1889", 1202161486)
]

let test (input, expected) = it ("aligns at " ++ show expected ++ " given a schedule of " ++ input) $ do
let Right busIds = parseBusIds input
busAlignment busIds `shouldBe` expected

for_ cases test

describe "crt" $ do
let cases :: [([(Integer, Integer)], (Integer, Integer), Integer)]
cases =
[ ([(2, 7), (0, 3), (1, 5)], (51, 105), 54),
([(2, 3), (3, 4), (1, 5)], (11, 60), 49),
([(0, 7), (1, 13), (4, 59), (6, 31), (7, 19)], (2093560, 3162341), 1068781),
([(0, 17), (2, 13), (3, 19)], (782, 4199), 3417),
([(0, 67), (1, 7), (2, 59), (3, 61)], (933913, 1687931), 754018),
([(0, 67), (2, 7), (3, 59), (4, 61)], (908721, 1687931), 779210),
([(0, 67), (1, 7), (3, 59), (4, 61)], (426455, 1687931), 1261476),
([(0, 1789), (1, 37), (2, 47), (3, 1889)], (4674651633, 5876813119), 1202161486),
([(0, 23), (17, 37), (23, 863), (35, 19), (36, 13), (40, 17), (52, 29), (54, 571), (95, 41)], (986925922963328, 2093650539157853), 1106724616194525)
]
let test (input, expected, expected') = it ("is " ++ show expected ++ " given " ++ show input) $ do
let output@(remainder, modulus) = crt input
output `shouldBe` expected
modulus - remainder `shouldBe` expected'

for_ cases test
2 changes: 2 additions & 0 deletions test/Day13/example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
939
7,13,x,x,59,x,31,19
2 changes: 2 additions & 0 deletions test/Day13/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1000052
23,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,37,x,x,x,x,x,863,x,x,x,x,x,x,x,x,x,x,x,19,13,x,x,x,17,x,x,x,x,x,x,x,x,x,x,x,29,x,571,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,41

0 comments on commit fda91f2

Please sign in to comment.