diff --git a/AdventOfCode2022.App/AdventOfCode2022.App.fsproj b/AdventOfCode2022.App/AdventOfCode2022.App.fsproj
index f17d599..1d22612 100644
--- a/AdventOfCode2022.App/AdventOfCode2022.App.fsproj
+++ b/AdventOfCode2022.App/AdventOfCode2022.App.fsproj
@@ -6,6 +6,9 @@
+
+
+
@@ -24,4 +27,8 @@
+
+
+
+
diff --git a/AdventOfCode2022.App/Assembly.fs b/AdventOfCode2022.App/Assembly.fs
new file mode 100644
index 0000000..305c1e9
--- /dev/null
+++ b/AdventOfCode2022.App/Assembly.fs
@@ -0,0 +1,18 @@
+namespace AdventOfCode2022.App
+
+open System.IO
+open System.Reflection
+
+[]
+module Assembly =
+ type private Dummy =
+ class
+ end
+
+ let readResource (name : string) : string =
+ let asm = Assembly.GetAssembly typeof
+
+ use stream = asm.GetManifestResourceStream $"AdventOfCode2022.App.%s{name}"
+
+ use reader = new StreamReader (stream)
+ reader.ReadToEnd ()
diff --git a/AdventOfCode2022.App/Inputs.fs b/AdventOfCode2022.App/Inputs.fs
new file mode 100644
index 0000000..3639126
--- /dev/null
+++ b/AdventOfCode2022.App/Inputs.fs
@@ -0,0 +1,6 @@
+namespace AdventOfCode2022.App
+
+[]
+module Inputs =
+ let days = Array.init 11 (fun day -> Assembly.readResource $"Day%i{day + 1}.txt")
+ let inline day (i : int) = days.[i - 1]
diff --git a/AdventOfCode2022.App/Program.fs b/AdventOfCode2022.App/Program.fs
index f5988d9..5fcf06f 100644
--- a/AdventOfCode2022.App/Program.fs
+++ b/AdventOfCode2022.App/Program.fs
@@ -4,84 +4,48 @@ open System.Diagnostics
open System.IO
open System.Reflection
open AdventOfCode2022
+open BenchmarkDotNet.Attributes
+open BenchmarkDotNet.Configs
+open BenchmarkDotNet.Running
+
+type Benchmarks () =
+ []
+ member _.Setup () = Run.shouldWrite <- false
+
+ []
+ member val IsPartOne = false with get, set
+
+ []
+ member val Day = 0 with get, set
+
+ []
+ member this.Benchmark () : unit =
+ Run.allRuns.[this.Day - 1] this.IsPartOne (Inputs.day this.Day)
+
+ []
+ member _.Cleanup () = Run.shouldWrite <- true
module Program =
- type Dummy =
- class
- end
-
- let readResource (name : string) : string =
- let asm = Assembly.GetAssembly typeof
-
- use stream = asm.GetManifestResourceStream $"AdventOfCode2022.App.%s{name}"
-
- use reader = new StreamReader (stream)
- reader.ReadToEnd ()
[]
- let main _ =
- let days = Array.init 11 (fun day -> readResource $"Day%i{day + 1}.txt")
+ let main args =
+ match args with
+ | [| "bench" |] ->
+ let config =
+ ManualConfig
+ .Create(DefaultConfig.Instance)
+ .WithOptions ConfigOptions.DisableOptimizationsValidator
- let inline day (i : int) = days.[i - 1]
+ let summary = BenchmarkRunner.Run config
+ 0
+ | _ ->
let time = Stopwatch.StartNew ()
time.Restart ()
- do
- let lines = StringSplitEnumerator.make '\n' (day 1)
- printfn "%i" (Day1.part1 lines)
- printfn "%i" (Day1.part2 lines)
-
- do
- let lines = StringSplitEnumerator.make '\n' (day 2)
- printfn "%i" (Day2.part1 lines)
- printfn "%i" (Day2.part2 lines)
-
- do
- let day3 = day 3
- let lines = StringSplitEnumerator.make '\n' day3
- printfn "%i" (Day3Efficient.part1 lines)
- printfn "%i" (Day3Efficient.part2 (day3.Split '\n'))
-
- do
- let lines = StringSplitEnumerator.make '\n' (day 4)
- printfn "%i" (Day4.part1 lines)
- printfn "%i" (Day4.part2 lines)
-
- do
- let lines = StringSplitEnumerator.make '\n' (day 5)
- printfn "%s" (Day5.part1 lines)
- printfn "%s" (Day5.part2 lines)
-
- do
- let day6 = day 6
- printfn "%i" (Day6.part1 day6)
- printfn "%i" (Day6.part2 day6)
-
- do
- let day7 = day(7).Split '\n'
- printfn "%i" (Day7.part1 day7)
- printfn "%i" (Day7.part2 day7)
-
- do
- let day8 = StringSplitEnumerator.make '\n' (day 8)
- printfn "%i" (Day8.part1 day8)
- printfn "%i" (Day8.part2 day8)
-
- do
- let day9 = StringSplitEnumerator.make '\n' (day 9)
- 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)
+ for day in 1 .. Run.allRuns.Length do
+ Run.allRuns.[day - 1] false (Inputs.day day)
+ Run.allRuns.[day - 1] true (Inputs.day day)
time.Stop ()
printfn $"Took %i{time.ElapsedMilliseconds}ms"
diff --git a/AdventOfCode2022.App/Run.fs b/AdventOfCode2022.App/Run.fs
new file mode 100644
index 0000000..19579cf
--- /dev/null
+++ b/AdventOfCode2022.App/Run.fs
@@ -0,0 +1,174 @@
+namespace AdventOfCode2022.App
+
+open AdventOfCode2022
+
+[]
+module Run =
+ let mutable shouldWrite = true
+
+ let day1 (partTwo : bool) (input : string) =
+ let lines = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day1.part1 lines
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day1.part2 lines
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day2 (partTwo : bool) (input : string) =
+ let lines = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day2.part1 lines
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day2.part2 lines
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day3 (partTwo : bool) (input : string) =
+ let lines = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day3Efficient.part1 lines
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day3Efficient.part2 (input.Split '\n')
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day4 (partTwo : bool) (input : string) =
+ let lines = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day4.part1 lines
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day4.part2 lines
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day5 (partTwo : bool) (input : string) =
+ let lines = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day5.part1 lines
+
+ if shouldWrite then
+ printfn "%s" output
+ else
+ let output = Day5.part2 lines
+
+ if shouldWrite then
+ printfn "%s" output
+
+ let day6 (partTwo : bool) (input : string) =
+ if not partTwo then
+ let output = Day6.part1 input
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day6.part2 input
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day7 (partTwo : bool) (input : string) =
+ let day7 = input.Split '\n'
+
+ if not partTwo then
+ let output = Day7.part1 day7
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day7.part2 day7
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day8 (partTwo : bool) (input : string) =
+ let day8 = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day8.part1 day8
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day8.part2 day8
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day9 (partTwo : bool) (input : string) =
+ let day9 = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day9.part1 day9
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day9.part2 day9
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let day10 (partTwo : bool) (input : string) =
+ let day10 = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day10.part1 day10
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day10.part2 day10
+
+ if shouldWrite then
+ Day10.render (printfn "%s") output
+
+ let day11 (partTwo : bool) (input : string) =
+ let day11 = StringSplitEnumerator.make '\n' input
+
+ if not partTwo then
+ let output = Day11.part1 day11
+
+ if shouldWrite then
+ printfn "%i" output
+ else
+ let output = Day11.part2 day11
+
+ if shouldWrite then
+ printfn "%i" output
+
+ let allRuns =
+ [|
+ day1
+ day2
+ day3
+ day4
+ day5
+ day6
+ day7
+ day8
+ day9
+ day10
+ day11
+ |]
diff --git a/AdventOfCode2022.Test/Day1.fs b/AdventOfCode2022.Test/Day1.fs
index ab961e1..05107ce 100644
--- a/AdventOfCode2022.Test/Day1.fs
+++ b/AdventOfCode2022.Test/Day1.fs
@@ -38,7 +38,6 @@ module TestDay1 =
let ``Part 1, given example`` () =
Day1.part1 (StringSplitEnumerator.make '\n' testInput) |> shouldEqual 24000
-
[]
let ``Part 2, given example`` () =
Day1.part2 (StringSplitEnumerator.make '\n' testInput) |> shouldEqual 45000
diff --git a/AdventOfCode2022.Test/Day11.fs b/AdventOfCode2022.Test/Day11.fs
index 5abc81c..d9d1913 100644
--- a/AdventOfCode2022.Test/Day11.fs
+++ b/AdventOfCode2022.Test/Day11.fs
@@ -44,7 +44,7 @@ Monkey 3:
[]
let ``Part 1, single round, given`` () =
let monkeys = Day11.parse (StringSplitEnumerator.make '\n' input)
- let inspections = Array.zeroCreate monkeys.Count
+ let inspections = Array.zeroCreate monkeys.Length
Day11.oneRoundDivThree monkeys inspections
diff --git a/AdventOfCode2022/Day11.fs b/AdventOfCode2022/Day11.fs
index d006c33..a08e3a1 100644
--- a/AdventOfCode2022/Day11.fs
+++ b/AdventOfCode2022/Day11.fs
@@ -17,13 +17,14 @@ module Day11 =
Number : int
StartingItems : int64 ResizeArray
OperationIsPlus : bool
- Argument : int64 option
+ /// Negative is None
+ Argument : int64
TestDivisibleBy : int64
TrueCase : int
FalseCase : int
}
- let parse (lines : StringSplitEnumerator) : Monkey IReadOnlyList =
+ let parse (lines : StringSplitEnumerator) : Monkey array =
use mutable enum = lines
let output = ResizeArray ()
@@ -107,10 +108,10 @@ module Day11 =
let arg =
if EfficientString.equals "old" enum.Current then
- None
+ -1L
else
let literal = Int64.Parse enum.Current
- Some literal
+ literal
if enum.MoveNext () then
failwith "too many entries on row"
@@ -170,7 +171,7 @@ module Day11 =
}
|> output.Add
- output :> IReadOnlyList<_>
+ output.ToArray ()
let oneRoundDivThree (monkeys : IReadOnlyList) (inspections : int64 array) =
for i in 0 .. monkeys.Count - 1 do
@@ -181,8 +182,8 @@ module Day11 =
let newWorry =
let arg =
match monkey.Argument with
- | None -> worry
- | Some l -> l
+ | -1L -> worry
+ | l -> l
if monkey.OperationIsPlus then worry + arg else worry * arg
@@ -201,7 +202,7 @@ module Day11 =
let part1 (lines : StringSplitEnumerator) : int64 =
let monkeys = parse lines
- let mutable inspections = Array.zeroCreate monkeys.Count
+ let mutable inspections = Array.zeroCreate monkeys.Length
for _round in 1..20 do
oneRoundDivThree monkeys inspections
@@ -209,8 +210,8 @@ module Day11 =
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 inline oneRound (modulus : int64) (monkeys : Monkey array) (inspections : int64 array) =
+ for i in 0 .. monkeys.Length - 1 do
let monkey = monkeys.[i]
inspections.[i] <- inspections.[i] + int64 monkey.StartingItems.Count
@@ -220,8 +221,8 @@ module Day11 =
let newWorry =
let arg =
match monkey.Argument with
- | None -> worry
- | Some l -> l
+ | -1L -> worry
+ | l -> l
if monkey.OperationIsPlus then worry + arg else worry * arg
@@ -241,7 +242,7 @@ module Day11 =
let part2 (lines : StringSplitEnumerator) : int64 =
let monkeys = parse lines
- let mutable inspections = Array.zeroCreate monkeys.Count
+ let mutable inspections = Array.zeroCreate monkeys.Length
let modulus =
(1L, monkeys) ||> Seq.fold (fun i monkey -> i * monkey.TestDivisibleBy)
diff --git a/AdventOfCode2022/Day8.fs b/AdventOfCode2022/Day8.fs
index 26013dc..811d100 100644
--- a/AdventOfCode2022/Day8.fs
+++ b/AdventOfCode2022/Day8.fs
@@ -6,7 +6,7 @@ open System
[]
module Day8 =
- let parse (lines : StringSplitEnumerator) : byte[] IReadOnlyList =
+ let parse (lines : StringSplitEnumerator) : byte[,] =
use mutable enum = lines
let output = ResizeArray ()
@@ -23,15 +23,15 @@ module Day8 =
output.Add arr
- output :> _
+ Array2D.init output.Count output.[0].Length (fun x y -> output.[x].[y])
- let isVisible (board : byte[] IReadOnlyList) (x : int) (y : int) : bool =
+ let isVisible (board : byte[,]) (x : int) (y : int) : bool =
// From the left?
let mutable isVisible = true
let mutable i = 0
while i < x && isVisible do
- if board.[y].[i] >= board.[y].[x] then
+ if board.[y, i] >= board.[y, x] then
isVisible <- false
i <- i + 1
@@ -42,10 +42,10 @@ module Day8 =
// From the right?
let mutable isVisible = true
- let mutable i = board.[0].Length - 1
+ let mutable i = board.GetLength 1 - 1
while i > x && isVisible do
- if board.[y].[i] >= board.[y].[x] then
+ if board.[y, i] >= board.[y, x] then
isVisible <- false
i <- i - 1
@@ -54,45 +54,45 @@ module Day8 =
true
else
- // From the top?
- let mutable isVisible = true
- let mutable i = 0
+ // From the top?
+ let mutable isVisible = true
+ let mutable i = 0
- while i < y && isVisible do
- if board.[i].[x] >= board.[y].[x] then
- isVisible <- false
+ while i < y && isVisible do
+ if board.[i, x] >= board.[y, x] then
+ isVisible <- false
- i <- i + 1
+ i <- i + 1
- if isVisible then
- true
- else
+ if isVisible then
+ true
+ else
- // From the bottom?
- let mutable isVisible = true
- let mutable i = board.Count - 1
+ // From the bottom?
+ let mutable isVisible = true
+ let mutable i = board.GetLength 0 - 1
- while i > y && isVisible do
- if board.[i].[x] >= board.[y].[x] then
- isVisible <- false
+ while i > y && isVisible do
+ if board.[i, x] >= board.[y, x] then
+ isVisible <- false
- i <- i - 1
+ i <- i - 1
- isVisible
+ isVisible
let part1 (lines : StringSplitEnumerator) : int =
let board = parse lines
let mutable visibleCount = 0
- for y in 0 .. board.Count - 1 do
- for x in 0 .. board.[0].Length - 1 do
+ for y in 0 .. board.GetLength 0 - 1 do
+ for x in 0 .. board.GetLength 1 - 1 do
if isVisible board x y then
visibleCount <- visibleCount + 1
visibleCount
- let scenicScore (board : byte[] IReadOnlyList) (x : int) (y : int) : int =
+ let scenicScore (board : byte[,]) (x : int) (y : int) : int =
let mutable scenicCount = 0
do
@@ -100,7 +100,7 @@ module Day8 =
let mutable i = y - 1
while i >= 0 && isVisible do
- if board.[i].[x] >= board.[y].[x] then
+ if board.[i, x] >= board.[y, x] then
isVisible <- false
scenicCount <- scenicCount + 1
@@ -112,8 +112,8 @@ module Day8 =
let mutable i = y + 1
let mutable subCount = 0
- while i < board.Count && isVisible do
- if board.[i].[x] >= board.[y].[x] then
+ while i < board.GetLength 0 && isVisible do
+ if board.[i, x] >= board.[y, x] then
isVisible <- false
subCount <- subCount + 1
@@ -128,7 +128,7 @@ module Day8 =
let mutable subCount = 0
while i >= 0 && isVisible do
- if board.[y].[i] >= board.[y].[x] then
+ if board.[y, i] >= board.[y, x] then
isVisible <- false
subCount <- subCount + 1
@@ -142,8 +142,8 @@ module Day8 =
let mutable i = x + 1
let mutable subCount = 0
- while i < board.[0].Length && isVisible do
- if board.[y].[i] >= board.[y].[x] then
+ while i < board.GetLength 1 && isVisible do
+ if board.[y, i] >= board.[y, x] then
isVisible <- false
subCount <- subCount + 1
@@ -159,8 +159,8 @@ module Day8 =
let board = parse lines
let mutable scenicMax = 0
- for y in 0 .. board.Count - 1 do
- for x in 0 .. board.[0].Length - 1 do
+ for y in 0 .. board.GetLength 0 - 1 do
+ for x in 0 .. board.GetLength 1 - 1 do
scenicMax <- max scenicMax (scenicScore board x y)
scenicMax
diff --git a/AdventOfCode2022/EfficientString.fs b/AdventOfCode2022/EfficientString.fs
index 612ba70..7b4a2e8 100644
--- a/AdventOfCode2022/EfficientString.fs
+++ b/AdventOfCode2022/EfficientString.fs
@@ -81,9 +81,13 @@ module StringSplitEnumerator =
SplitOn = splitChar
}
- let chomp (s : string) (e : byref) =
+ let chomp (s : string) (e : byref) : unit =
+#if DEBUG
if not (e.MoveNext ()) || not (EfficientString.equals s e.Current) then
failwithf "expected '%s', got '%s'" s (e.Current.ToString ())
+#else
+ e.MoveNext () |> ignore
+#endif
let consumeInt (e : byref) : int =
if not (e.MoveNext ()) then
diff --git a/README.md b/README.md
index cd1a108..1c914db 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,34 @@
Advent of Code 2022, in F#.
Just `dotnet build` and `dotnet test`.
+
+## Perf
+
+As of Day 11:
+
+```
+| Method | IsPartOne | Day | Mean | Error | StdDev |
+|---------- |---------- |---- |-------------:|-----------:|-----------:|
+| Benchmark | False | 1 | 32.663 us | 0.4192 us | 0.3921 us |
+| Benchmark | False | 2 | 82.502 us | 0.4576 us | 0.4281 us |
+| Benchmark | False | 3 | 33.273 us | 0.5546 us | 0.5188 us |
+| Benchmark | False | 4 | 66.689 us | 0.3763 us | 0.2938 us |
+| Benchmark | False | 5 | 96.317 us | 1.4445 us | 1.3511 us |
+| Benchmark | False | 6 | 90.087 us | 0.7930 us | 0.6622 us |
+| Benchmark | False | 7 | 466.332 us | 5.0471 us | 4.7211 us |
+| Benchmark | False | 8 | 759.873 us | 4.3523 us | 4.0712 us |
+| Benchmark | False | 9 | 507.427 us | 5.8451 us | 5.4675 us |
+| Benchmark | False | 10 | 7.615 us | 0.0300 us | 0.0280 us |
+| Benchmark | False | 11 | 12.617 us | 0.0716 us | 0.0670 us |
+| Benchmark | True | 1 | 33.206 us | 0.1680 us | 0.1572 us |
+| Benchmark | True | 2 | 80.874 us | 0.3673 us | 0.3436 us |
+| Benchmark | True | 3 | 72.505 us | 0.8570 us | 0.8016 us |
+| Benchmark | True | 4 | 56.584 us | 0.5950 us | 0.5565 us |
+| Benchmark | True | 5 | 84.942 us | 0.4420 us | 0.4135 us |
+| Benchmark | True | 6 | 167.142 us | 1.0515 us | 0.9836 us |
+| Benchmark | True | 7 | 454.487 us | 3.4531 us | 2.8835 us |
+| Benchmark | True | 8 | 370.147 us | 2.0985 us | 1.9630 us |
+| Benchmark | True | 9 | 938.836 us | 10.7999 us | 9.0184 us |
+| Benchmark | True | 10 | 8.446 us | 0.0685 us | 0.0641 us |
+| Benchmark | True | 11 | 3,768.481 us | 15.3375 us | 14.3467 us |
+```