From 4205ec46616cca6a65444d063507dddf5aae6723 Mon Sep 17 00:00:00 2001 From: Patrick Stevens Date: Sun, 11 Dec 2022 08:58:28 +0000 Subject: [PATCH] Day 11 (#14) --- .github/workflows/dotnet.yaml | 12 +- .../AdventOfCode2022.App.fsproj | 1 + AdventOfCode2022.App/Program.fs | 8 +- .../AdventOfCode2022.Test.fsproj | 2 + AdventOfCode2022.Test/Day11.fs | 67 +++++ AdventOfCode2022.Test/Inputs/Day11.txt | 55 ++++ AdventOfCode2022/AdventOfCode2022.fsproj | 1 + AdventOfCode2022/Day11.fs | 253 ++++++++++++++++++ AdventOfCode2022/EfficientString.fs | 2 +- 9 files changed, 395 insertions(+), 6 deletions(-) create mode 100644 AdventOfCode2022.Test/Day11.fs create mode 100644 AdventOfCode2022.Test/Inputs/Day11.txt create mode 100644 AdventOfCode2022/Day11.fs diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml index e735ffd..2b9dde4 100644 --- a/.github/workflows/dotnet.yaml +++ b/.github/workflows/dotnet.yaml @@ -13,6 +13,12 @@ env: jobs: build: + strategy: + matrix: + config: + - Release + - Debug + runs-on: ubuntu-latest steps: @@ -24,11 +30,11 @@ jobs: - name: Restore dependencies run: dotnet restore - name: Build - run: dotnet build --no-restore + run: dotnet build --no-restore --configuration ${{matrix.config}} - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test --no-build --verbosity normal --configuration ${{matrix.config}} - name: Run app - run: dotnet run --project AdventOfCode2022.App/AdventOfCode2022.App.fsproj --no-build + run: dotnet run --project AdventOfCode2022.App/AdventOfCode2022.App.fsproj --no-build --configuration ${{matrix.config}} check-format: runs-on: ubuntu-latest diff --git a/AdventOfCode2022.App/AdventOfCode2022.App.fsproj b/AdventOfCode2022.App/AdventOfCode2022.App.fsproj index d528b33..f17d599 100644 --- a/AdventOfCode2022.App/AdventOfCode2022.App.fsproj +++ b/AdventOfCode2022.App/AdventOfCode2022.App.fsproj @@ -17,6 +17,7 @@ + diff --git a/AdventOfCode2022.App/Program.fs b/AdventOfCode2022.App/Program.fs index eb41f1a..f5988d9 100644 --- a/AdventOfCode2022.App/Program.fs +++ b/AdventOfCode2022.App/Program.fs @@ -20,7 +20,7 @@ module Program = [] let main _ = - let days = Array.init 10 (fun day -> readResource $"Day%i{day + 1}.txt") + let days = Array.init 11 (fun day -> readResource $"Day%i{day + 1}.txt") let inline day (i : int) = days.[i - 1] @@ -73,12 +73,16 @@ module Program = printfn "%i" (Day9.part1 day9) printfn "%i" (Day9.part2 day9) - do let day10 = StringSplitEnumerator.make '\n' (day 10) printfn "%i" (Day10.part1 day10) Day10.render (printfn "%s") (Day10.part2 day10) + do + let day11 = StringSplitEnumerator.make '\n' (day 11) + printfn "%i" (Day11.part1 day11) + printfn "%i" (Day11.part2 day11) + time.Stop () printfn $"Took %i{time.ElapsedMilliseconds}ms" 0 diff --git a/AdventOfCode2022.Test/AdventOfCode2022.Test.fsproj b/AdventOfCode2022.Test/AdventOfCode2022.Test.fsproj index 0adbe1f..9fc17e4 100644 --- a/AdventOfCode2022.Test/AdventOfCode2022.Test.fsproj +++ b/AdventOfCode2022.Test/AdventOfCode2022.Test.fsproj @@ -18,6 +18,7 @@ + @@ -28,6 +29,7 @@ + diff --git a/AdventOfCode2022.Test/Day11.fs b/AdventOfCode2022.Test/Day11.fs new file mode 100644 index 0000000..5abc81c --- /dev/null +++ b/AdventOfCode2022.Test/Day11.fs @@ -0,0 +1,67 @@ +namespace AdventOfCode2022.Test + +open NUnit.Framework +open FsUnitTyped +open AdventOfCode2022 + +[] +module TestDay11 = + + let input = + """Monkey 0: + Starting items: 79, 98 + Operation: new = old * 19 + Test: divisible by 23 + If true: throw to monkey 2 + If false: throw to monkey 3 + +Monkey 1: + Starting items: 54, 65, 75, 74 + Operation: new = old + 6 + Test: divisible by 19 + If true: throw to monkey 2 + If false: throw to monkey 0 + +Monkey 2: + Starting items: 79, 60, 97 + Operation: new = old * old + Test: divisible by 13 + If true: throw to monkey 1 + If false: throw to monkey 3 + +Monkey 3: + Starting items: 74 + Operation: new = old + 3 + Test: divisible by 17 + If true: throw to monkey 0 + If false: throw to monkey 1 +""" + + [] + let ``Part 1, given`` () = + Day11.part1 (StringSplitEnumerator.make '\n' input) |> shouldEqual 10605 + + [] + let ``Part 1, single round, given`` () = + let monkeys = Day11.parse (StringSplitEnumerator.make '\n' input) + let inspections = Array.zeroCreate monkeys.Count + + Day11.oneRoundDivThree monkeys inspections + + inspections |> shouldEqual [| 2 ; 4 ; 3 ; 5 |] + + [] + let ``Part 1`` () = + let input = Assembly.readResource "Day11.txt" + + Day11.part1 (StringSplitEnumerator.make '\n' input) |> shouldEqual 120384 + + + [] + let ``Part 2, given 1`` () = + Day11.part2 (StringSplitEnumerator.make '\n' input) |> shouldEqual 2713310158L + + [] + let ``Part 2`` () = + let input = Assembly.readResource "Day11.txt" + Day11.part2 (StringSplitEnumerator.make '\n' input) |> shouldEqual 32059801242L diff --git a/AdventOfCode2022.Test/Inputs/Day11.txt b/AdventOfCode2022.Test/Inputs/Day11.txt new file mode 100644 index 0000000..065a235 --- /dev/null +++ b/AdventOfCode2022.Test/Inputs/Day11.txt @@ -0,0 +1,55 @@ +Monkey 0: + Starting items: 99, 67, 92, 61, 83, 64, 98 + Operation: new = old * 17 + Test: divisible by 3 + If true: throw to monkey 4 + If false: throw to monkey 2 + +Monkey 1: + Starting items: 78, 74, 88, 89, 50 + Operation: new = old * 11 + Test: divisible by 5 + If true: throw to monkey 3 + If false: throw to monkey 5 + +Monkey 2: + Starting items: 98, 91 + Operation: new = old + 4 + Test: divisible by 2 + If true: throw to monkey 6 + If false: throw to monkey 4 + +Monkey 3: + Starting items: 59, 72, 94, 91, 79, 88, 94, 51 + Operation: new = old * old + Test: divisible by 13 + If true: throw to monkey 0 + If false: throw to monkey 5 + +Monkey 4: + Starting items: 95, 72, 78 + Operation: new = old + 7 + Test: divisible by 11 + If true: throw to monkey 7 + If false: throw to monkey 6 + +Monkey 5: + Starting items: 76 + Operation: new = old + 8 + Test: divisible by 17 + If true: throw to monkey 0 + If false: throw to monkey 2 + +Monkey 6: + Starting items: 69, 60, 53, 89, 71, 88 + Operation: new = old + 5 + Test: divisible by 19 + If true: throw to monkey 7 + If false: throw to monkey 1 + +Monkey 7: + Starting items: 72, 54, 63, 80 + Operation: new = old + 3 + Test: divisible by 7 + If true: throw to monkey 1 + If false: throw to monkey 3 diff --git a/AdventOfCode2022/AdventOfCode2022.fsproj b/AdventOfCode2022/AdventOfCode2022.fsproj index d2f5ced..a9c251b 100644 --- a/AdventOfCode2022/AdventOfCode2022.fsproj +++ b/AdventOfCode2022/AdventOfCode2022.fsproj @@ -19,6 +19,7 @@ + diff --git a/AdventOfCode2022/Day11.fs b/AdventOfCode2022/Day11.fs new file mode 100644 index 0000000..d006c33 --- /dev/null +++ b/AdventOfCode2022/Day11.fs @@ -0,0 +1,253 @@ +namespace AdventOfCode2022 + +open System.Collections.Generic +open System +#if DEBUG +open Checked +#endif + +[] +type monkey + +[] +module Day11 = + + type Monkey = + { + Number : int + StartingItems : int64 ResizeArray + OperationIsPlus : bool + Argument : int64 option + TestDivisibleBy : int64 + TrueCase : int + FalseCase : int + } + + let parse (lines : StringSplitEnumerator) : Monkey IReadOnlyList = + use mutable enum = lines + let output = ResizeArray () + + while enum.MoveNext () do + let monkey = + let line = enum.Current.TrimEnd () + let mutable enum = StringSplitEnumerator.make' ' ' (line.TrimEnd ':') + StringSplitEnumerator.chomp "Monkey" &enum + let monkey = StringSplitEnumerator.consumeInt &enum * 1 + + if enum.MoveNext () then + failwith "Bad Monkey row" + + monkey + + if not (enum.MoveNext ()) then + failwith "Ran out of rows" + + let line = enum.Current + + let startItems = + let line = line.Trim () + let mutable enum = StringSplitEnumerator.make' ' ' line + StringSplitEnumerator.chomp "Starting" &enum + StringSplitEnumerator.chomp "items:" &enum + let items = ResizeArray () + + while enum.MoveNext () do + let s = enum.Current.TrimEnd ',' + items.Add (Int64.Parse s) + + items + + if not (enum.MoveNext ()) then + failwith "Ran out of rows" + + let line = enum.Current + + let operationIsPlus, arg = + let line = line.Trim () + let mutable enum = StringSplitEnumerator.make' ':' line + StringSplitEnumerator.chomp "Operation" &enum + + if not (enum.MoveNext ()) then + failwith "expected an operation" + + let line = enum.Current.Trim () + + if enum.MoveNext () then + failwith "bad formatting on operation" + + let mutable enum = StringSplitEnumerator.make' '=' line + StringSplitEnumerator.chomp "new " &enum + + if not (enum.MoveNext ()) then + failwith "expected an RHS" + + let rhs = enum.Current.Trim () + + if enum.MoveNext () then + failwith "too many equals signs" + + let mutable enum = StringSplitEnumerator.make' ' ' rhs + + StringSplitEnumerator.chomp "old" &enum + + if not (enum.MoveNext ()) then + failwith "expected three elements on RHS" + + let operationIsPlus = + if enum.Current.Length > 1 then + failwith "expected operation of exactly 1 char" + + match enum.Current.[0] with + | '*' -> false + | '+' -> true + | c -> failwithf "Unrecognised op: %c" c + + if not (enum.MoveNext ()) then + failwith "expected three elements on RHS" + + let arg = + if EfficientString.equals "old" enum.Current then + None + else + let literal = Int64.Parse enum.Current + Some literal + + if enum.MoveNext () then + failwith "too many entries on row" + + operationIsPlus, arg + + if not (enum.MoveNext ()) then + failwith "Ran out of rows" + + let line = enum.Current.Trim () + + let test = + if not (line.StartsWith "Test: divisible by ") then + failwith "bad formatting on test line" + + Int32.Parse (line.Slice 19) + + if not (enum.MoveNext ()) then + failwith "Ran out of rows" + + let line = enum.Current.Trim () + + let ifTrue = + if not (line.StartsWith "If true: throw to monkey ") then + failwith "bad formatting for ifTrue line" + + Int32.Parse (line.Slice 24) * 1 + + if not (enum.MoveNext ()) then + failwith "Ran out of rows" + + let line = enum.Current.Trim () + + let ifFalse = + if not (line.StartsWith "If false: throw to monkey ") then + failwith "bad formatting for ifFalse line" + + Int32.Parse (line.Slice 25) * 1 + + // We may be at the end, in which case there's no empty row. + enum.MoveNext () |> ignore + + if ifTrue = monkey then + failwith "assumption broken: throws to self" + + if ifFalse = monkey then + failwith "assumption broken: throws to self" + + { + Number = monkey + StartingItems = startItems + OperationIsPlus = operationIsPlus + Argument = arg + TestDivisibleBy = test + TrueCase = ifTrue + FalseCase = ifFalse + } + |> output.Add + + output :> IReadOnlyList<_> + + let oneRoundDivThree (monkeys : IReadOnlyList) (inspections : int64 array) = + for i in 0 .. monkeys.Count - 1 do + let monkey = monkeys.[i] + inspections.[i] <- inspections.[i] + int64 monkey.StartingItems.Count + + for worry in monkey.StartingItems do + let newWorry = + let arg = + match monkey.Argument with + | None -> worry + | Some l -> l + + if monkey.OperationIsPlus then worry + arg else worry * arg + + let newWorry = newWorry / 3L + + let target = + if newWorry % monkey.TestDivisibleBy = 0 then + monkey.TrueCase + else + monkey.FalseCase + + monkeys.[target / 1].StartingItems.Add newWorry + + monkey.StartingItems.Clear () + + let part1 (lines : StringSplitEnumerator) : int64 = + let monkeys = parse lines + + let mutable inspections = Array.zeroCreate monkeys.Count + + for _round in 1..20 do + oneRoundDivThree monkeys inspections + + inspections |> Array.sortInPlace + inspections.[inspections.Length - 1] * inspections.[inspections.Length - 2] + + let oneRound (modulus : int64) (monkeys : IReadOnlyList) (inspections : int64 array) = + for i in 0 .. monkeys.Count - 1 do + let monkey = monkeys.[i] + inspections.[i] <- inspections.[i] + int64 monkey.StartingItems.Count + + for worryIndex in 0 .. monkey.StartingItems.Count - 1 do + let worry = monkey.StartingItems.[worryIndex] + + let newWorry = + let arg = + match monkey.Argument with + | None -> worry + | Some l -> l + + if monkey.OperationIsPlus then worry + arg else worry * arg + + let newWorry = newWorry % modulus + + let target = + if newWorry % monkey.TestDivisibleBy = 0 then + monkey.TrueCase + else + monkey.FalseCase + + monkeys.[target / 1].StartingItems.Add newWorry + + monkey.StartingItems.Clear () + + + let part2 (lines : StringSplitEnumerator) : int64 = + let monkeys = parse lines + + let mutable inspections = Array.zeroCreate monkeys.Count + + let modulus = + (1L, monkeys) ||> Seq.fold (fun i monkey -> i * monkey.TestDivisibleBy) + + for _round in 1..10000 do + oneRound modulus monkeys inspections + + inspections |> Array.sortInPlace + inspections.[inspections.Length - 1] * inspections.[inspections.Length - 2] diff --git a/AdventOfCode2022/EfficientString.fs b/AdventOfCode2022/EfficientString.fs index 80d11fd..612ba70 100644 --- a/AdventOfCode2022/EfficientString.fs +++ b/AdventOfCode2022/EfficientString.fs @@ -83,7 +83,7 @@ module StringSplitEnumerator = let chomp (s : string) (e : byref) = if not (e.MoveNext ()) || not (EfficientString.equals s e.Current) then - failwithf "expected '%s'" s + failwithf "expected '%s', got '%s'" s (e.Current.ToString ()) let consumeInt (e : byref) : int = if not (e.MoveNext ()) then