This commit is contained in:
Patrick Stevens
2022-12-11 08:58:28 +00:00
committed by GitHub
parent c5b360acfa
commit 4205ec4661
9 changed files with 395 additions and 6 deletions

View File

@@ -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

View File

@@ -17,6 +17,7 @@
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day8.txt" />
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day9.txt" />
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day10.txt" />
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day11.txt" />
</ItemGroup>
<ItemGroup>

View File

@@ -20,7 +20,7 @@ module Program =
[<EntryPoint>]
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

View File

@@ -18,6 +18,7 @@
<Compile Include="Day8.fs" />
<Compile Include="Day9.fs" />
<Compile Include="Day10.fs" />
<Compile Include="Day11.fs" />
<EmbeddedResource Include="Inputs\Day1.txt" />
<EmbeddedResource Include="Inputs\Day2.txt" />
<EmbeddedResource Include="Inputs\Day3.txt" />
@@ -28,6 +29,7 @@
<EmbeddedResource Include="Inputs\Day8.txt" />
<EmbeddedResource Include="Inputs\Day9.txt" />
<EmbeddedResource Include="Inputs\Day10.txt" />
<EmbeddedResource Include="Inputs\Day11.txt" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,67 @@
namespace AdventOfCode2022.Test
open NUnit.Framework
open FsUnitTyped
open AdventOfCode2022
[<TestFixture>]
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
"""
[<Test>]
let ``Part 1, given`` () =
Day11.part1 (StringSplitEnumerator.make '\n' input) |> shouldEqual 10605
[<Test>]
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 |]
[<Test>]
let ``Part 1`` () =
let input = Assembly.readResource "Day11.txt"
Day11.part1 (StringSplitEnumerator.make '\n' input) |> shouldEqual 120384
[<Test>]
let ``Part 2, given 1`` () =
Day11.part2 (StringSplitEnumerator.make '\n' input) |> shouldEqual 2713310158L
[<Test>]
let ``Part 2`` () =
let input = Assembly.readResource "Day11.txt"
Day11.part2 (StringSplitEnumerator.make '\n' input) |> shouldEqual 32059801242L

View File

@@ -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

View File

@@ -19,6 +19,7 @@
<Compile Include="Day8.fs" />
<Compile Include="Day9.fs" />
<Compile Include="Day10.fs" />
<Compile Include="Day11.fs" />
</ItemGroup>
</Project>

253
AdventOfCode2022/Day11.fs Normal file
View File

@@ -0,0 +1,253 @@
namespace AdventOfCode2022
open System.Collections.Generic
open System
#if DEBUG
open Checked
#endif
[<Measure>]
type monkey
[<RequireQualifiedAccess>]
module Day11 =
type Monkey =
{
Number : int<monkey>
StartingItems : int64 ResizeArray
OperationIsPlus : bool
Argument : int64 option
TestDivisibleBy : int64
TrueCase : int<monkey>
FalseCase : int<monkey>
}
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<monkey>
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<monkey>
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<monkey>
// 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<Monkey>) (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<monkey>].StartingItems.Add newWorry
monkey.StartingItems.Clear ()
let part1 (lines : StringSplitEnumerator) : int64 =
let monkeys = parse lines
let mutable inspections = Array.zeroCreate<int64> 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<Monkey>) (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<monkey>].StartingItems.Add newWorry
monkey.StartingItems.Clear ()
let part2 (lines : StringSplitEnumerator) : int64 =
let monkeys = parse lines
let mutable inspections = Array.zeroCreate<int64> 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]

View File

@@ -83,7 +83,7 @@ module StringSplitEnumerator =
let chomp (s : string) (e : byref<StringSplitEnumerator>) =
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<StringSplitEnumerator>) : int =
if not (e.MoveNext ()) then