diff --git a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs index 3390fa9..cc19451 100644 --- a/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs +++ b/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.Lib/Day5.fs @@ -80,63 +80,120 @@ module Day5 = best - let private split (ranges : (uint64 * uint64) ResizeArray) (newRange : Range) : unit = + // The input ranges are inclusive at both ends. + let private split'' (ranges : (uint64 * uint64) ResizeArray) sourceStart sourceLen : unit = let startsStack = ResizeArray () let endsStack = ResizeArray () - startsStack.Add newRange.SourceStart - endsStack.Add (newRange.SourceStart + newRange.Len - 1uL) + startsStack.Add sourceStart + endsStack.Add (sourceStart + sourceLen - 1uL) while startsStack.Count > 0 do let mutable i = 0 while i < ranges.Count do let low, high = ranges.[i] - if low <= startsStack.[startsStack.Count - 1] && endsStack.[endsStack.Count - 1] <= high then + if startsStack.Count > 0 && low <= startsStack.[startsStack.Count - 1] && endsStack.[endsStack.Count - 1] <= high then // splitting because: // low ... start .. finish .. high - ranges.[i] <- (low, startsStack.[startsStack.Count - 1]) + ranges.[i] <- (low, startsStack.[startsStack.Count - 1] - 1uL) ranges.Add (startsStack.[startsStack.Count - 1], endsStack.[endsStack.Count - 1]) - ranges.Add (endsStack.[endsStack.Count - 1], high) + ranges.Add (endsStack.[endsStack.Count - 1] + 1uL, high) startsStack.RemoveAt (startsStack.Count - 1) endsStack.RemoveAt (endsStack.Count - 1) - elif low <= startsStack.[startsStack.Count - 1] && high < endsStack.[endsStack.Count - 1] then + elif startsStack.Count > 0 && low <= startsStack.[startsStack.Count - 1] && startsStack.[startsStack.Count - 1] <= high then // splitting because: // low .. start .. high .. finish - ranges.[i] <- (low, startsStack.[startsStack.Count - 1]) + ranges.[i] <- (low, startsStack.[startsStack.Count - 1] - 1uL) ranges.Add (startsStack.[startsStack.Count - 1], high) - startsStack.[startsStack.Count - 1] <- high - elif low > startsStack.[startsStack.Count - 1] && low < newRange.SourceStart + newRange.Len && newRange.SourceStart + newRange.Len < high then + startsStack.[startsStack.Count - 1] <- high + 1uL + elif startsStack.Count > 0 && low > startsStack.[startsStack.Count - 1] && low < endsStack.[endsStack.Count - 1] && endsStack.[endsStack.Count - 1] <= high then // splitting because: // start .. low .. finish .. high ranges.[i] <- (low, endsStack.[endsStack.Count - 1]) ranges.Add (endsStack.[endsStack.Count - 1], high) - endsStack.[endsStack.Count - 1] <- low - elif low > newRange.SourceStart && low < newRange.SourceStart + newRange.Len && high < newRange.SourceStart + newRange.Len then + endsStack.[endsStack.Count - 1] <- low - 1uL + elif startsStack.Count > 0 && low > startsStack.[startsStack.Count - 1] && low < endsStack.[endsStack.Count - 1] && high < endsStack.[endsStack.Count - 1] then // splitting because: // start .. low .. high .. finish - failwith "" + startsStack.Add (high + 1uL) + endsStack.Add endsStack.[endsStack.Count - 1] + endsStack.[endsStack.Count - 2] <- low - 1uL i <- i + 1 + // The input ranges are inclusive at both ends. + // Returns any range we didn't map. + let private split (result : ResizeArray) (start, finish) (rangeFromLayer : Range) : (uint64 * uint64 * (uint64 * uint64) voption) voption = + let low, high = rangeFromLayer.SourceStart, 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 + result.Add (start - rangeFromLayer.SourceStart + rangeFromLayer.DestStart, finish - rangeFromLayer.SourceStart + rangeFromLayer.DestStart) + 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. + result.Add (start - rangeFromLayer.SourceStart + rangeFromLayer.DestStart, high - rangeFromLayer.SourceStart + rangeFromLayer.DestStart) + 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 + result.Add (low - rangeFromLayer.SourceStart + rangeFromLayer.DestStart, high - rangeFromLayer.SourceStart + rangeFromLayer.DestStart) + 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 + result.Add (low - rangeFromLayer.SourceStart + rangeFromLayer.DestStart, finish - rangeFromLayer.SourceStart + rangeFromLayer.DestStart) + ValueSome (start, low - 1uL, ValueNone) + else + ValueSome (start, finish, ValueNone) + let part2 (s : string) : uint64 = let seeds, mappings = parse s - let chunks = seeds |> Array.splitInto (seeds.Length / 2) - chunks - |> Array.Parallel.map (fun arr -> - let start, len = match arr with | [| s ; l |] -> s, l | _ -> failwith "bad" - let mutable best = UInt64.MaxValue - for seed in start .. start + len - uint64 1 do - let mutable remapped = seed - for map in mappings do - let mutable hasRemappedThisLayer = false - for interval in map do - if not hasRemappedThisLayer then - match remap interval remapped with - | ValueNone -> - () - | ValueSome n -> - hasRemappedThisLayer <- true - remapped <- n - if remapped < best then - best <- remapped - best - ) - |> Array.min + let mutable intervals = ResizeArray () + + for i = 0 to (seeds.Length - 1) / 2 do + let t = seeds.[2 * i], seeds.[2 * i + 1] + seeds.[2 * i] - 1uL + intervals.Add t + + let mutable nextIntervals = ResizeArray () + + for mapLayer in mappings do + let mutable i = 0 + while i < intervals.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 nextIntervals intervals.[i] range with + | ValueNone -> + allMoved <- true + | ValueSome (start, finish, v) -> + intervals.[i] <- (start, finish) + match v with + | ValueNone -> () + | ValueSome (start, finish) -> + intervals.Add (start, finish) + currentRange <- currentRange + 1 + if not allMoved then + nextIntervals.Add intervals.[i] + i <- i + 1 + let oldIntervals = intervals + oldIntervals.Clear () + intervals <- nextIntervals + nextIntervals <- oldIntervals + + let mutable best = UInt64.MaxValue + for i, _ in intervals do + best <- min best i + + best diff --git a/AdventOfCode2023.FSharp/Test/TestDay5.fs b/AdventOfCode2023.FSharp/Test/TestDay5.fs index feb770d..818ce20 100644 --- a/AdventOfCode2023.FSharp/Test/TestDay5.fs +++ b/AdventOfCode2023.FSharp/Test/TestDay5.fs @@ -12,10 +12,10 @@ module TestDay5 = let sample = Assembly.getEmbeddedResource typeof.Assembly "day5.txt" [] - let part1Sample () = sample |> Day5.part1 |> shouldEqual (uint64 35) + let part1Sample () = sample |> Day5.part1 |> shouldEqual 35uL [] - let part2Sample () = sample |> Day5.part2 |> shouldEqual (uint64 46) + let part2Sample () = sample |> Day5.part2 |> shouldEqual 46uL [] let part1Actual () = @@ -28,7 +28,7 @@ module TestDay5 = Assert.Inconclusive () failwith "unreachable" - Day5.part1 s |> shouldEqual (uint64 806029445) + Day5.part1 s |> shouldEqual 806029445uL [] let part2Actual () = @@ -41,4 +41,4 @@ module TestDay5 = Assert.Inconclusive () failwith "unreachable" - Day5.part2 s |> shouldEqual (uint64 59370572) + Day5.part2 s |> shouldEqual 59370572uL