diff --git a/AdventOfCode2023.Tests/Day05_Tests.cs b/AdventOfCode2023.Tests/Day05_Tests.cs index cde6463..8affeed 100644 --- a/AdventOfCode2023.Tests/Day05_Tests.cs +++ b/AdventOfCode2023.Tests/Day05_Tests.cs @@ -59,7 +59,7 @@ public class Day05_Tests } [Fact] - public void AlamanacMapping_ParseNext__Empty__Null() + public void AlmanacMapping_ParseNext__Empty__Null() { Day05.LinesReader reader = new(Array.Empty()); Day05.AlmanacMapping? mapping = Day05.AlmanacMapping.ParseNext(reader); @@ -68,7 +68,7 @@ public class Day05_Tests } [Fact] - public void AlamanacMapping_ParseNext__Example1() + public void AlmanacMapping_ParseNext__Example1() { Day05.LinesReader reader = new(new[] { "seed-to-soil map:", @@ -82,21 +82,197 @@ public class Day05_Tests Assert.Equal(2, mapping.RangeMappings.Count); Assert.Equal(50, mapping.RangeMappings[0].DestinationStart); Assert.Equal(98, mapping.RangeMappings[0].OriginStart); - Assert.Equal(2, mapping.RangeMappings[0].Lenght); + Assert.Equal(2, mapping.RangeMappings[0].Length); Assert.Equal(52, mapping.RangeMappings[1].DestinationStart); Assert.Equal(50, mapping.RangeMappings[1].OriginStart); - Assert.Equal(48, mapping.RangeMappings[1].Lenght); + Assert.Equal(48, mapping.RangeMappings[1].Length); - long value1 = 100; + const long value1 = 100; long valueMapped1 = mapping.Apply(value1); Assert.Equal(100, valueMapped1); - long value2 = 99; + const long value2 = 99; long valueMapped2 = mapping.Apply(value2); Assert.Equal(51, valueMapped2); - long value3 = 45; + const long value3 = 45; long valueMapped3 = mapping.Apply(value3); Assert.Equal(45, valueMapped3); } + + [Fact] + public void AlmanacRangeMapping_Clip__Examples() + { + // ..........■■■■■■■■■■.......... + Day05.AlmanacRangeMapping rangeMapping = new() { + OriginStart = 10, + DestinationStart = 1000, + Length = 10, + }; + + // ..........■■■■■■■■■■.......... + // #####......................... + Day05.AlmanacRangeMapping range_Lower = new() { + OriginStart = 0, + DestinationStart = 0, + Length = 5, + }; + Day05.AlmanacRangeMapping.ClipResult range_Lower_Result = rangeMapping.Clip(range_Lower); + Assert.NotNull(range_Lower_Result.PreClip); + Assert.Equal(0, range_Lower_Result.PreClip.Value.OriginStart); + Assert.Equal(0, range_Lower_Result.PreClip.Value.DestinationStart); + Assert.Equal(5, range_Lower_Result.PreClip.Value.Length); + Assert.Null(range_Lower_Result.Clipped); + Assert.Null(range_Lower_Result.PostClip); + + // ..........■■■■■■■■■■.......... + // ##########.................... + Day05.AlmanacRangeMapping range_LowerTouching = new() { + OriginStart = 0, + DestinationStart = 0, + Length = 10, + }; + Day05.AlmanacRangeMapping.ClipResult range_LowerTouching_Result = rangeMapping.Clip(range_LowerTouching); + Assert.NotNull(range_LowerTouching_Result.PreClip); + Assert.Equal(0, range_LowerTouching_Result.PreClip.Value.OriginStart); + Assert.Equal(0, range_LowerTouching_Result.PreClip.Value.DestinationStart); + Assert.Equal(10, range_LowerTouching_Result.PreClip.Value.Length); + Assert.Null(range_LowerTouching_Result.Clipped); + Assert.Null(range_LowerTouching_Result.PostClip); + + // ..........■■■■■■■■■■.......... + // .........................##### + Day05.AlmanacRangeMapping range_Upper = new() { + OriginStart = 25, + DestinationStart = 25, + Length = 5, + }; + Day05.AlmanacRangeMapping.ClipResult range_Upper_Result = rangeMapping.Clip(range_Upper); + Assert.Null(range_Upper_Result.PreClip); + Assert.Null(range_Upper_Result.Clipped); + Assert.NotNull(range_Upper_Result.PostClip); + Assert.Equal(25, range_Upper_Result.PostClip.Value.OriginStart); + Assert.Equal(25, range_Upper_Result.PostClip.Value.DestinationStart); + Assert.Equal(5, range_Upper_Result.PostClip.Value.Length); + + // ..........■■■■■■■■■■.......... + // ....................########## + Day05.AlmanacRangeMapping range_UpperTouching = new() { + OriginStart = 20, + DestinationStart = 20, + Length = 10, + }; + Day05.AlmanacRangeMapping.ClipResult range_UpperTouching_Result = rangeMapping.Clip(range_UpperTouching); + Assert.Null(range_UpperTouching_Result.PreClip); + Assert.Null(range_UpperTouching_Result.Clipped); + Assert.NotNull(range_UpperTouching_Result.PostClip); + Assert.Equal(20, range_UpperTouching_Result.PostClip.Value.OriginStart); + Assert.Equal(20, range_UpperTouching_Result.PostClip.Value.DestinationStart); + Assert.Equal(10, range_UpperTouching_Result.PostClip.Value.Length); + + // ..........■■■■■■■■■■.......... + // ..........$$$$$$$$$$.......... + Day05.AlmanacRangeMapping range_IntersectCover = new() { + OriginStart = 10, + DestinationStart = 10, + Length = 10, + }; + Day05.AlmanacRangeMapping.ClipResult range_IntersectCover_Result = rangeMapping.Clip(range_IntersectCover); + Assert.Null(range_IntersectCover_Result.PreClip); + Assert.NotNull(range_IntersectCover_Result.Clipped); + Assert.Equal(10, range_IntersectCover_Result.Clipped.Value.OriginStart); + Assert.Equal(1000, range_IntersectCover_Result.Clipped.Value.DestinationStart); + Assert.Equal(10, range_IntersectCover_Result.Clipped.Value.Length); + Assert.Null(range_IntersectCover_Result.PostClip); + + // ..........■■■■■■■■■■.......... + // ...............$$$$$.......... + Day05.AlmanacRangeMapping range_IntersectInsideToEnd = new() { + OriginStart = 15, + DestinationStart = 15, + Length = 5, + }; + Day05.AlmanacRangeMapping.ClipResult range_IntersectInsideToEnd_Result = rangeMapping.Clip(range_IntersectInsideToEnd); + Assert.Null(range_IntersectInsideToEnd_Result.PreClip); + Assert.NotNull(range_IntersectInsideToEnd_Result.Clipped); + Assert.Equal(15, range_IntersectInsideToEnd_Result.Clipped.Value.OriginStart); + Assert.Equal(1005, range_IntersectInsideToEnd_Result.Clipped.Value.DestinationStart); + Assert.Equal(5, range_IntersectInsideToEnd_Result.Clipped.Value.Length); + Assert.Null(range_IntersectInsideToEnd_Result.PostClip); + + // ..........■■■■■■■■■■.......... + // ...............$$$$$#####..... + Day05.AlmanacRangeMapping range_IntersectInsideToOutside = new() { + OriginStart = 15, + DestinationStart = 15, + Length = 10, + }; + Day05.AlmanacRangeMapping.ClipResult range_IntersectInsideToOutside_Result = rangeMapping.Clip(range_IntersectInsideToOutside); + Assert.Null(range_IntersectInsideToOutside_Result.PreClip); + Assert.NotNull(range_IntersectInsideToOutside_Result.Clipped); + Assert.Equal(15, range_IntersectInsideToOutside_Result.Clipped.Value.OriginStart); + Assert.Equal(1005, range_IntersectInsideToOutside_Result.Clipped.Value.DestinationStart); + Assert.Equal(5, range_IntersectInsideToOutside_Result.Clipped.Value.Length); + Assert.NotNull(range_IntersectInsideToOutside_Result.PostClip); + Assert.Equal(20, range_IntersectInsideToOutside_Result.PostClip.Value.OriginStart); + Assert.Equal(20, range_IntersectInsideToOutside_Result.PostClip.Value.DestinationStart); + Assert.Equal(5, range_IntersectInsideToOutside_Result.PostClip.Value.Length); + + // ..........■■■■■■■■■■.......... + // .....#####$$$$$............... + Day05.AlmanacRangeMapping range_IntersectOutsideToInside = new() { + OriginStart = 5, + DestinationStart = 5, + Length = 10, + }; + Day05.AlmanacRangeMapping.ClipResult range_IntersectOutsideToInside_Result = rangeMapping.Clip(range_IntersectOutsideToInside); + Assert.NotNull(range_IntersectOutsideToInside_Result.PreClip); + Assert.Equal(5, range_IntersectOutsideToInside_Result.PreClip.Value.OriginStart); + Assert.Equal(5, range_IntersectOutsideToInside_Result.PreClip.Value.DestinationStart); + Assert.Equal(5, range_IntersectOutsideToInside_Result.PreClip.Value.Length); + Assert.NotNull(range_IntersectOutsideToInside_Result.Clipped); + Assert.Equal(10, range_IntersectOutsideToInside_Result.Clipped.Value.OriginStart); + Assert.Equal(1000, range_IntersectOutsideToInside_Result.Clipped.Value.DestinationStart); + Assert.Equal(5, range_IntersectOutsideToInside_Result.Clipped.Value.Length); + Assert.Null(range_IntersectOutsideToInside_Result.PostClip); + + // ..........■■■■■■■■■■.......... + // .....#####$$$$$$$$$$.......... + Day05.AlmanacRangeMapping range_IntersectOutsideToEnd = new() { + OriginStart = 5, + DestinationStart = 5, + Length = 15, + }; + Day05.AlmanacRangeMapping.ClipResult range_IntersectOutsideToEnd_Result = rangeMapping.Clip(range_IntersectOutsideToEnd); + Assert.NotNull(range_IntersectOutsideToEnd_Result.PreClip); + Assert.Equal(5, range_IntersectOutsideToEnd_Result.PreClip.Value.OriginStart); + Assert.Equal(5, range_IntersectOutsideToEnd_Result.PreClip.Value.DestinationStart); + Assert.Equal(5, range_IntersectOutsideToEnd_Result.PreClip.Value.Length); + Assert.NotNull(range_IntersectOutsideToEnd_Result.Clipped); + Assert.Equal(10, range_IntersectOutsideToEnd_Result.Clipped.Value.OriginStart); + Assert.Equal(1000, range_IntersectOutsideToEnd_Result.Clipped.Value.DestinationStart); + Assert.Equal(10, range_IntersectOutsideToEnd_Result.Clipped.Value.Length); + Assert.Null(range_IntersectOutsideToEnd_Result.PostClip); + + // ..........■■■■■■■■■■.......... + // .....#####$$$$$$$$$$#####..... + Day05.AlmanacRangeMapping range_IntersectOutsideToOutside = new() { + OriginStart = 5, + DestinationStart = 5, + Length = 20, + }; + Day05.AlmanacRangeMapping.ClipResult range_IntersectOutsideToOutside_Result = rangeMapping.Clip(range_IntersectOutsideToOutside); + Assert.NotNull(range_IntersectOutsideToOutside_Result.PreClip); + Assert.Equal(5, range_IntersectOutsideToOutside_Result.PreClip.Value.OriginStart); + Assert.Equal(5, range_IntersectOutsideToOutside_Result.PreClip.Value.DestinationStart); + Assert.Equal(5, range_IntersectOutsideToOutside_Result.PreClip.Value.Length); + Assert.NotNull(range_IntersectOutsideToOutside_Result.Clipped); + Assert.Equal(10, range_IntersectOutsideToOutside_Result.Clipped.Value.OriginStart); + Assert.Equal(1000, range_IntersectOutsideToOutside_Result.Clipped.Value.DestinationStart); + Assert.Equal(10, range_IntersectOutsideToOutside_Result.Clipped.Value.Length); + Assert.NotNull(range_IntersectOutsideToOutside_Result.PostClip); + Assert.Equal(20, range_IntersectOutsideToOutside_Result.PostClip.Value.OriginStart); + Assert.Equal(20, range_IntersectOutsideToOutside_Result.PostClip.Value.DestinationStart); + Assert.Equal(5, range_IntersectOutsideToOutside_Result.PostClip.Value.Length); + } } \ No newline at end of file diff --git a/AdventOfCode2023/Day05.cs b/AdventOfCode2023/Day05.cs index cf02ad0..bbd3f23 100644 --- a/AdventOfCode2023/Day05.cs +++ b/AdventOfCode2023/Day05.cs @@ -135,19 +135,22 @@ public class Day05 : IDay { LinesReader reader = new(inputs); Almanac? almanac = Almanac.Parse(reader); - long minLocation = long.MaxValue; int i = 0; + List ranges = new(); while (almanac?.Seeds.Count > i) { - long seed = almanac?.Seeds[i] ?? 0; - long seedLen = almanac?.Seeds[i + 1] ?? 0; - for (long j = 0; j < seedLen; j++) - { - long currentSeed = almanac?.ApplyMapping(seed + j) ?? 0; - if (currentSeed < minLocation) { minLocation = currentSeed; } - } + long seed = almanac.Seeds[i]; + long seedLen = almanac.Seeds[i + 1]; + AlmanacRangeMapping range = new() { + OriginStart = seed, + DestinationStart = seed, + Length = seedLen, + }; + ranges.Add(range); i += 2; } + List mappedRanges = almanac?.ApplyMapping(ranges) ?? ranges; + long minLocation = mappedRanges.Select(range => range.DestinationStart).Min(); return minLocation.ToString(); } @@ -176,15 +179,119 @@ public class Day05 : IDay { public long DestinationStart { get; init; } public long OriginStart { get; init; } - public long Lenght { get; init; } + public long Length { get; init; } public long? Apply(long value) { if (value < OriginStart) { return null; } long diff = value - OriginStart; - if (diff >= Lenght) { return null; } + if (diff >= Length) { return null; } return DestinationStart + diff; } + + public class ClipResult + { + public AlmanacRangeMapping? Clipped { get; init; } + public AlmanacRangeMapping? PreClip { get; init; } + public AlmanacRangeMapping? PostClip { get; init; } + } + + public ClipResult Clip(AlmanacRangeMapping range) + { + long rangeOriginEnd = range.OriginStart + range.Length; + + if (rangeOriginEnd < OriginStart) + { + return new ClipResult { PreClip = range, }; + } + + long originEnd = OriginStart + Length; + + if (originEnd < range.OriginStart) + { + return new ClipResult { PostClip = range, }; + } + + if (OriginStart <= range.OriginStart) + { + if (range.OriginStart == originEnd) + { + return new ClipResult() { + PostClip = range, + }; + } + + if (rangeOriginEnd <= originEnd) + { + long lenInside = range.OriginStart - OriginStart; + return new ClipResult() { + Clipped = range with { + DestinationStart = DestinationStart + lenInside, + }, + }; + } + + if (rangeOriginEnd >= originEnd) + { + long lenInside = range.OriginStart - OriginStart; + long lenOverlap = originEnd - range.OriginStart; + return new ClipResult() { + Clipped = range with { + DestinationStart = DestinationStart + lenInside, + Length = Length - lenInside, + }, + PostClip = new AlmanacRangeMapping { + OriginStart = range.OriginStart + lenOverlap, + DestinationStart = range.DestinationStart + lenOverlap, + Length = range.Length - lenOverlap, + }, + }; + } + } + + if (range.OriginStart < OriginStart) + { + if (rangeOriginEnd <= originEnd) + { + long lenClipped = (rangeOriginEnd) - OriginStart; + long lenPre = range.Length - lenClipped; + + return new ClipResult() { + PreClip = range with { + Length = lenPre, + }, + Clipped = lenClipped > 0 + ? this with { + Length = lenClipped, + } + : null, + }; + } + + if (rangeOriginEnd > originEnd) + { + long lenClipped = Length; + long lenPre = OriginStart - range.OriginStart; + long lenToPost = originEnd - range.OriginStart; + long lenPost = rangeOriginEnd - originEnd; + return new ClipResult() { + PreClip = range with { + Length = lenPre, + }, + Clipped = this with { + Length = lenClipped, + }, + PostClip = new AlmanacRangeMapping { + OriginStart = range.OriginStart + lenToPost, + DestinationStart = range.DestinationStart + lenToPost, + Length = lenPost, + }, + }; + } + } + + return new ClipResult(); + } } public class AlmanacMapping @@ -217,7 +324,7 @@ public class Day05 : IDay AlmanacRangeMapping rangeMapping = new() { DestinationStart = Convert.ToInt64(mappingParts[0]), OriginStart = Convert.ToInt64(mappingParts[1]), - Lenght = Convert.ToInt64(mappingParts[2]), + Length = Convert.ToInt64(mappingParts[2]), }; mapping.RangeMappings.Add(rangeMapping); } while (true); @@ -236,6 +343,33 @@ public class Day05 : IDay } return value; } + + public List Apply(AlmanacRangeMapping range) + { + List unMappedRanges = new() { + range, + }; + List newUnMappedRanges = new(); + List mappedRanges = new(); + + int i = 0; + while (RangeMappings.Count > i) + { + AlmanacRangeMapping rangeMapping = RangeMappings[i]; + for (int j = 0; j < unMappedRanges.Count; j++) + { + AlmanacRangeMapping.ClipResult result = rangeMapping.Clip(unMappedRanges[j]); + if (result.Clipped != null) { mappedRanges.Add(result.Clipped.Value); } + if (result.PreClip != null) { newUnMappedRanges.Add(result.PreClip.Value); } + if (result.PostClip != null) { newUnMappedRanges.Add(result.PostClip.Value); } + } + unMappedRanges = newUnMappedRanges; + newUnMappedRanges = new List(); + i++; + } + + return mappedRanges.Union(unMappedRanges).ToList(); + } } public class Almanac @@ -277,5 +411,23 @@ public class Day05 : IDay } return value; } + + public List ApplyMapping(List ranges) + { + List currentRanges = ranges; + int i = 0; + while (i < Mappings.Count) + { + List mappedRanges = currentRanges + .Select(range => Mappings[i].Apply(range)) + .SelectMany(x => x) + .ToList(); + currentRanges = mappedRanges + .Select(x => x with { OriginStart = x.DestinationStart, }) + .ToList(); + i++; + } + return currentRanges; + } } } \ No newline at end of file