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 | +```