diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Inputs.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Inputs.fs index 2d29639..58cd579 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Inputs.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Inputs.fs @@ -14,6 +14,6 @@ module Inputs = if isNull dir then failwith "reached root of filesystem without finding inputs dir" - Array.init 14 (fun day -> Path.Combine (dir.FullName, "inputs", $"day%i{day + 1}.txt") |> File.ReadAllText) + Array.init 15 (fun day -> Path.Combine (dir.FullName, "inputs", $"day%i{day + 1}.txt") |> File.ReadAllText) let inline day (i : int) = days.[i - 1] diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Program.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Program.fs index 85029c0..8de9c54 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Program.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Program.fs @@ -44,7 +44,7 @@ module Benchmarks = [] member _.Setup () = Run.shouldWrite <- false - [] + [] member val Day = 0 with get, set [] diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Run.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Run.fs index 90c329c..8f7a676 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Run.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Bench/Run.fs @@ -197,6 +197,18 @@ module Run = if shouldWrite then Console.WriteLine output + let day15 (partTwo : bool) (input : string) = + if not partTwo then + let output = Day15.part1 input + + if shouldWrite then + Console.WriteLine output + else + let output = Day15.part2 input + + if shouldWrite then + Console.WriteLine output + let allRuns = [| day1 @@ -213,4 +225,5 @@ module Run = day12 day13 day14 + day15 |] diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj index 1290625..8bdcbba 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj @@ -25,6 +25,7 @@ + diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day15.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day15.fs new file mode 100644 index 0000000..fa6171a --- /dev/null +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day15.fs @@ -0,0 +1,114 @@ +namespace AdventOfCode2023 + +open System +open System.Globalization + +[] +module Day15 = + + let hash (s : ReadOnlySpan) : int = + let mutable v = 0 + + for c in s do + v <- v + int (byte c) + v <- (17 * v) % 256 + + v + + let part1 (s : string) = + let s = s.AsSpan().TrimEnd () + use chunks = StringSplitEnumerator.make' ',' s + let mutable answer = 0 + + for chunk in chunks do + answer <- answer + hash chunk + + answer + + let inline removeFirst<'a> ([] toRemove : 'a -> bool) (arr : ResizeArray<'a>) : unit = + let mutable i = 0 + + while i < arr.Count do + if toRemove arr.[i] then + for j = i to arr.Count - 2 do + arr.[j] <- arr.[j + 1] + + arr.RemoveAt (arr.Count - 1) + i <- arr.Count + + i <- i + 1 + + let inline replace + ([] withKey : 'a -> 'key) + (key : 'key) + (value : 'a) + (arr : ResizeArray<'a>) + : unit + = + let mutable i = 0 + + while i < arr.Count do + if withKey arr.[i] = key then + arr.[i] <- value + i <- arr.Count + + i <- i + 1 + + if i < arr.Count + 1 then + // no replacement was made + arr.Add value + + let inline getLength (labelAndLength : uint64) : uint32 = + (labelAndLength % uint64 UInt32.MaxValue) |> uint32 + + let inline getLabel (labelAndLength : uint64) : uint32 = + (labelAndLength / uint64 UInt32.MaxValue) |> uint32 + + let inline focusingPower (boxNumber : uint32) (arr : ResizeArray<_>) = + let mutable answer = 0ul + + for i = 0 to arr.Count - 1 do + answer <- answer + (boxNumber + 1ul) * (uint32 i + 1ul) * getLength arr.[i] + + answer + + let inline toUint32 (s : ReadOnlySpan) : uint32 = + let mutable answer = 0ul + + for c in s do + answer <- answer * 26ul + uint32 (byte c - byte 'a') + + answer + + let inline pack (label : uint32) (focalLength : uint32) : uint64 = + uint64 label * uint64 UInt32.MaxValue + uint64 focalLength + + let part2 (s : string) = + let s = s.AsSpan().TrimEnd () + use chunks = StringSplitEnumerator.make' ',' s + // The max length of a label turns out to be 6, which means we need 26^6 < 2^32 entries. + // So we'll use a uint32 instead of our string, to save hopping around memory. + // We'll also pack the focal length into the elements, to save tupling. + let lenses = Array.init 256 (fun _ -> ResizeArray ()) + + for chunk in chunks do + if chunk.[chunk.Length - 1] = '-' then + let label = chunk.Slice (0, chunk.Length - 1) + let labelShrunk = toUint32 label + removeFirst (fun labelAndLength -> getLabel labelAndLength = labelShrunk) lenses.[hash label] + else + let equalsPos = chunk.IndexOf '=' + + let focalLength = + UInt32.Parse (chunk.Slice (equalsPos + 1), NumberStyles.None, CultureInfo.InvariantCulture) + + let label = chunk.Slice (0, equalsPos) + let labelShrunk = toUint32 label + replace getLabel labelShrunk (pack labelShrunk focalLength) lenses.[hash label] + + let mutable answer = 0ul + + for i = 0 to 255 do + answer <- answer + focusingPower (uint32 i) lenses.[i] + + answer diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs index 8689c4b..9e7b12d 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs @@ -284,6 +284,22 @@ module Program = Console.WriteLine (part2.ToString ()) Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms") + Console.WriteLine "=====Day 15=====" + + do + let input = Path.Combine (dir.FullName, "day15.txt") |> File.ReadAllText + + sw.Restart () + let part1 = Day15.part1 input + sw.Stop () + Console.WriteLine (part1.ToString ()) + Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms") + sw.Restart () + let part2 = Day15.part2 input + sw.Stop () + Console.WriteLine (part2.ToString ()) + Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms") + endToEnd.Stop () Console.Error.WriteLine ( diff --git a/AdventOfCode2023.FSharp/Test/Test.fsproj b/AdventOfCode2023.FSharp/Test/Test.fsproj index 75f79a1..2a6094c 100644 --- a/AdventOfCode2023.FSharp/Test/Test.fsproj +++ b/AdventOfCode2023.FSharp/Test/Test.fsproj @@ -23,6 +23,7 @@ + @@ -40,6 +41,7 @@ + diff --git a/AdventOfCode2023.FSharp/Test/TestDay15.fs b/AdventOfCode2023.FSharp/Test/TestDay15.fs new file mode 100644 index 0000000..29b3519 --- /dev/null +++ b/AdventOfCode2023.FSharp/Test/TestDay15.fs @@ -0,0 +1,46 @@ +namespace AdventOfCode2023.Test + +open AdventOfCode2023 +open NUnit.Framework +open FsUnitTyped +open System.IO + +[] +module TestDay15 = + + [] + let sample = Assembly.getEmbeddedResource typeof.Assembly "day15.txt" + + [] + let part1Sample () = + sample |> Day15.part1 |> shouldEqual 1320 + + [] + let part2Sample () = + sample |> Day15.part2 |> shouldEqual 145ul + + [] + let part1Actual () = + let s = + try + File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day15.txt")) + with + | :? DirectoryNotFoundException + | :? FileNotFoundException -> + Assert.Inconclusive () + failwith "unreachable" + + Day15.part1 s |> shouldEqual 521434 + + [] + let part2Actual () = + let s = + try + File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day15.txt")) + with + | :? DirectoryNotFoundException + | :? FileNotFoundException -> + Assert.Inconclusive () + failwith "unreachable" + + Day15.part2 s |> shouldEqual 248279ul diff --git a/AdventOfCode2023.FSharp/Test/samples/day15.txt b/AdventOfCode2023.FSharp/Test/samples/day15.txt new file mode 100644 index 0000000..4f58f74 --- /dev/null +++ b/AdventOfCode2023.FSharp/Test/samples/day15.txt @@ -0,0 +1 @@ +rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7