From 9c48e5fa9699ce5e69fbdd6faecef394f432e141 Mon Sep 17 00:00:00 2001 From: patrick Date: Tue, 5 Dec 2023 20:26:07 +0000 Subject: [PATCH] Day 5 (#4) Co-authored-by: Smaug123 Reviewed-on: https://gitea.patrickstevens.co.uk/patrick/advent-of-code-2023/pulls/4 --- .../AdventOfCode2023.FSharp.Lib.fsproj | 1 + .../AdventOfCode2023.FSharp.Lib/Day4.fs | 24 ++- .../AdventOfCode2023.FSharp.Lib/Day5.fs | 191 ++++++++++++++++++ .../EfficientString.fs | 12 ++ .../AdventOfCode2023.FSharp/Program.fs | 23 +++ AdventOfCode2023.FSharp/Test/Test.fsproj | 2 + AdventOfCode2023.FSharp/Test/TestDay5.fs | 46 +++++ AdventOfCode2023.FSharp/Test/samples/day5.txt | 33 +++ 8 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs create mode 100644 AdventOfCode2023.FSharp/Test/TestDay5.fs create mode 100644 AdventOfCode2023.FSharp/Test/samples/day5.txt diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj index 29333a6..d989f2b 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/AdventOfCode2023.FSharp.Lib.fsproj @@ -12,6 +12,7 @@ + diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day4.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day4.fs index 50c2b3d..69ea3d3 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day4.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day4.fs @@ -1,15 +1,22 @@ namespace AdventOfCode2023 open System -open System.Collections.Generic +open System.Globalization [] module Day4 = + let inline parseByte (chars : ReadOnlySpan) : byte = + Byte.Parse (chars, NumberStyles.None, NumberFormatInfo.InvariantInfo) + //let mutable answer = 0uy + //for c in chars do + // answer <- answer * 10uy + (byte c - 48uy) + //answer + let part1 (s : string) = use lines = StringSplitEnumerator.make '\n' s let mutable total = 0 - let winningNumbers = HashSet () + let winningNumbers = ResizeArray () for line in lines do if not (line.IsWhiteSpace ()) then @@ -30,14 +37,14 @@ module Day4 = if split.Current.[0] = '|' then accumulatingWinning <- false else - winningNumbers.Add (Int32.Parse split.Current) |> ignore + winningNumbers.Add (parseByte split.Current) split.MoveNext () |> ignore let mutable answer = 0 while split.MoveNext () do if not split.Current.IsEmpty then - let n = Int32.Parse split.Current + let n = parseByte split.Current if winningNumbers.Contains n then answer <- answer + 1 @@ -51,9 +58,8 @@ module Day4 = let part2 (s : string) = use lines = StringSplitEnumerator.make '\n' s - let mutable total = 0 - let winningNumbers = HashSet () - let winners = ResizeArray () + let winningNumbers = ResizeArray () + let winners = ResizeArray () for line in lines do if not (line.IsWhiteSpace ()) then @@ -74,14 +80,14 @@ module Day4 = if split.Current.[0] = '|' then accumulatingWinning <- false else - winningNumbers.Add (Int32.Parse split.Current) |> ignore + winningNumbers.Add (parseByte split.Current) split.MoveNext () |> ignore let mutable answer = 0 while split.MoveNext () do if not split.Current.IsEmpty then - let n = Int32.Parse split.Current + let n = parseByte split.Current if winningNumbers.Contains n then answer <- answer + 1 diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs new file mode 100644 index 0000000..94338da --- /dev/null +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs @@ -0,0 +1,191 @@ +namespace AdventOfCode2023 + +open System + +[] +type Range = + { + SourceStart : uint32 + DestStart : uint32 + Len : uint32 + } + +[] +module Day5 = + + let parse (s : string) = + use mutable lines = StringSplitEnumerator.make '\n' s + lines.MoveNext () |> ignore + + let seeds = + use mutable line1 = StringSplitEnumerator.make' ' ' lines.Current + StringSplitEnumerator.chomp "seeds:" &line1 + let result = ResizeArray () + + while line1.MoveNext () do + result.Add (UInt32.Parse line1.Current) + + result.ToArray () + + lines.MoveNext () |> ignore + + let mappings = ResizeArray () + + let mutable currentMapping = null + + for line in lines do + if line.IsEmpty then + if not (isNull currentMapping) then + mappings.Add currentMapping + currentMapping <- null + else if isNull currentMapping then + currentMapping <- ResizeArray () + else + use mutable line = StringSplitEnumerator.make' ' ' line + let destStart = StringSplitEnumerator.consumeU32 &line + let sourceStart = StringSplitEnumerator.consumeU32 &line + let rangeLen = StringSplitEnumerator.consumeU32 &line + + { + SourceStart = sourceStart + DestStart = destStart + Len = rangeLen + } + |> currentMapping.Add + + seeds, mappings + + let part1 (s : string) = + let seeds, mappings = parse s + + let mutable best = UInt32.MaxValue + + for seed in seeds do + let mutable remapped = seed + + for map in mappings do + let mutable hasRemappedThisLayer = false + + for interval in map do + if not hasRemappedThisLayer then + if + interval.SourceStart <= remapped + && remapped - interval.SourceStart < interval.Len + then + hasRemappedThisLayer <- true + remapped <- remapped + (interval.DestStart - interval.SourceStart) + + if remapped < best then + best <- remapped + + best + + // The input ranges are inclusive at both ends. + // Returns any range we didn't map. + let private split + (resultStarts : ResizeArray) + (resultEnds : ResizeArray) + start + finish + (rangeFromLayer : Range) + : (uint32 * uint32 * (uint32 * uint32) voption) voption + = + let low = rangeFromLayer.SourceStart + let high = rangeFromLayer.SourceStart + rangeFromLayer.Len - 1ul + + if low <= start then + if finish <= high then + // low ... start .. finish .. high + // so the entire input range gets mapped down + resultStarts.Add (start + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + resultEnds.Add (finish + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + + ValueNone + elif start <= high then + // low .. start .. high .. finish + // so start .. high gets mapped down + // and high + 1 .. finish stays where it is. + // high < finish is already guaranteed by previous if block. + resultStarts.Add (start + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + resultEnds.Add (high + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + + ValueSome (high + 1ul, finish, ValueNone) + else + ValueSome (start, finish, ValueNone) + else if high <= finish then + // start .. low .. high .. finish + // so start .. low - 1 stays where it is + // low .. high gets mapped down + // and high + 1 .. finish stays where it is + resultStarts.Add (low + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + resultEnds.Add (high + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + + ValueSome (start, low - 1ul, ValueSome (high + 1ul, finish)) + elif low < finish then + // start .. low .. finish .. high + // so start .. low - 1 stays where it is + // and low .. finish gets mapped down + resultStarts.Add (low + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + resultEnds.Add (finish + rangeFromLayer.DestStart - rangeFromLayer.SourceStart) + + ValueSome (start, low - 1ul, ValueNone) + else + ValueSome (start, finish, ValueNone) + + let part2 (s : string) : uint32 = + let seeds, mappings = parse s + + let mutable intervalStarts = ResizeArray () + let mutable intervalEnds = ResizeArray () + + for i = 0 to (seeds.Length - 1) / 2 do + intervalStarts.Add seeds.[2 * i] + intervalEnds.Add (seeds.[2 * i + 1] + seeds.[2 * i] - 1ul) + + let mutable nextIntervalStarts = ResizeArray () + let mutable nextIntervalEnds = ResizeArray () + + for mapLayer in mappings do + let mutable i = 0 + + while i < intervalStarts.Count do + // split interval according to every map + let mutable allMoved = false + let mutable currentRange = 0 + + while not allMoved && currentRange < mapLayer.Count do + let range = mapLayer.[currentRange] + // range is e.g. 50 98 2, i.e. "98-99 goes to 50-51" + match split nextIntervalStarts nextIntervalEnds intervalStarts.[i] intervalEnds.[i] range with + | ValueNone -> allMoved <- true + | ValueSome (start, finish, v) -> + intervalStarts.[i] <- start + intervalEnds.[i] <- finish + + match v with + | ValueNone -> () + | ValueSome (start, finish) -> + intervalStarts.Add start + intervalEnds.Add finish + + currentRange <- currentRange + 1 + + if not allMoved then + nextIntervalStarts.Add intervalStarts.[i] + nextIntervalEnds.Add intervalEnds.[i] + + i <- i + 1 + + let oldIntervals = intervalStarts + oldIntervals.Clear () + intervalStarts <- nextIntervalStarts + nextIntervalStarts <- oldIntervals + + let oldIntervals = intervalEnds + oldIntervals.Clear () + intervalEnds <- nextIntervalEnds + nextIntervalEnds <- oldIntervals + + + // SIMD go brrr + System.Linq.Enumerable.Min intervalStarts diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/EfficientString.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/EfficientString.fs index 16eb2fb..fdfc2f4 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/EfficientString.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/EfficientString.fs @@ -95,3 +95,15 @@ module StringSplitEnumerator = failwith "expected an int, got nothing" Int32.Parse e.Current + + let consumeU32 (e : byref) : uint32 = + if not (e.MoveNext ()) then + failwith "expected an int, got nothing" + + UInt32.Parse e.Current + + let consumeU64 (e : byref) : uint64 = + if not (e.MoveNext ()) then + failwith "expected an int, got nothing" + + UInt64.Parse e.Current diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs index 0f1bfbb..94fa667 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/Program.fs @@ -23,6 +23,8 @@ module Program = let sw = Stopwatch.StartNew () + printfn "=====Day 1=====" + do sw.Restart () let input = Path.Combine (dir.FullName, "day1.txt") |> File.ReadAllText @@ -36,6 +38,8 @@ module Program = Console.WriteLine (part2.ToString ()) Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms") + printfn "=====Day 2=====" + do let input = Path.Combine (dir.FullName, "day2.txt") |> File.ReadAllText sw.Restart () @@ -49,6 +53,8 @@ module Program = Console.WriteLine (part2.ToString ()) Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms") + printfn "=====Day 3=====" + do let input = Path.Combine (dir.FullName, "day3.txt") |> File.ReadAllBytes @@ -85,6 +91,8 @@ module Program = Console.WriteLine (part2.ToString ()) Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms") + printfn "=====Day 4=====" + do let input = Path.Combine (dir.FullName, "day4.txt") |> File.ReadAllText sw.Restart () @@ -98,6 +106,21 @@ module Program = Console.WriteLine (part2.ToString ()) Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms") + printfn "=====Day 5=====" + + do + let input = Path.Combine (dir.FullName, "day5.txt") |> File.ReadAllText + sw.Restart () + let part1 = Day5.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 = Day5.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 bb7f579..aa21125 100644 --- a/AdventOfCode2023.FSharp/Test/Test.fsproj +++ b/AdventOfCode2023.FSharp/Test/Test.fsproj @@ -13,11 +13,13 @@ + + diff --git a/AdventOfCode2023.FSharp/Test/TestDay5.fs b/AdventOfCode2023.FSharp/Test/TestDay5.fs new file mode 100644 index 0000000..2739873 --- /dev/null +++ b/AdventOfCode2023.FSharp/Test/TestDay5.fs @@ -0,0 +1,46 @@ +namespace AdventOfCode2023.Test + +open System +open AdventOfCode2023 +open NUnit.Framework +open FsUnitTyped +open System.IO + +[] +module TestDay5 = + + let sample = Assembly.getEmbeddedResource typeof.Assembly "day5.txt" + + [] + let part1Sample () = + sample |> Day5.part1 |> shouldEqual 35ul + + [] + let part2Sample () = + sample |> Day5.part2 |> shouldEqual 46ul + + [] + let part1Actual () = + let s = + try + File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day5.txt")) + with + | :? DirectoryNotFoundException + | :? FileNotFoundException -> + Assert.Inconclusive () + failwith "unreachable" + + Day5.part1 s |> shouldEqual 806029445ul + + [] + let part2Actual () = + let s = + try + File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day5.txt")) + with + | :? DirectoryNotFoundException + | :? FileNotFoundException -> + Assert.Inconclusive () + failwith "unreachable" + + Day5.part2 s |> shouldEqual 59370572ul diff --git a/AdventOfCode2023.FSharp/Test/samples/day5.txt b/AdventOfCode2023.FSharp/Test/samples/day5.txt new file mode 100644 index 0000000..f756727 --- /dev/null +++ b/AdventOfCode2023.FSharp/Test/samples/day5.txt @@ -0,0 +1,33 @@ +seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48 + +soil-to-fertilizer map: +0 15 37 +37 52 2 +39 0 15 + +fertilizer-to-water map: +49 53 8 +0 11 42 +42 0 7 +57 7 4 + +water-to-light map: +88 18 7 +18 25 70 + +light-to-temperature map: +45 77 23 +81 45 19 +68 64 13 + +temperature-to-humidity map: +0 69 1 +1 0 69 + +humidity-to-location map: +60 56 37 +56 93 4