AdventOfCode 2023 Day05 Part2, Optimized using range mapping clipping

This commit is contained in:
2023-12-07 21:10:43 +01:00
parent 55487f7b61
commit 3e8fa75253
2 changed files with 346 additions and 18 deletions

View File

@@ -59,7 +59,7 @@ public class Day05_Tests
} }
[Fact] [Fact]
public void AlamanacMapping_ParseNext__Empty__Null() public void AlmanacMapping_ParseNext__Empty__Null()
{ {
Day05.LinesReader reader = new(Array.Empty<string>()); Day05.LinesReader reader = new(Array.Empty<string>());
Day05.AlmanacMapping? mapping = Day05.AlmanacMapping.ParseNext(reader); Day05.AlmanacMapping? mapping = Day05.AlmanacMapping.ParseNext(reader);
@@ -68,7 +68,7 @@ public class Day05_Tests
} }
[Fact] [Fact]
public void AlamanacMapping_ParseNext__Example1() public void AlmanacMapping_ParseNext__Example1()
{ {
Day05.LinesReader reader = new(new[] { Day05.LinesReader reader = new(new[] {
"seed-to-soil map:", "seed-to-soil map:",
@@ -82,21 +82,197 @@ public class Day05_Tests
Assert.Equal(2, mapping.RangeMappings.Count); Assert.Equal(2, mapping.RangeMappings.Count);
Assert.Equal(50, mapping.RangeMappings[0].DestinationStart); Assert.Equal(50, mapping.RangeMappings[0].DestinationStart);
Assert.Equal(98, mapping.RangeMappings[0].OriginStart); 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(52, mapping.RangeMappings[1].DestinationStart);
Assert.Equal(50, mapping.RangeMappings[1].OriginStart); 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); long valueMapped1 = mapping.Apply(value1);
Assert.Equal(100, valueMapped1); Assert.Equal(100, valueMapped1);
long value2 = 99; const long value2 = 99;
long valueMapped2 = mapping.Apply(value2); long valueMapped2 = mapping.Apply(value2);
Assert.Equal(51, valueMapped2); Assert.Equal(51, valueMapped2);
long value3 = 45; const long value3 = 45;
long valueMapped3 = mapping.Apply(value3); long valueMapped3 = mapping.Apply(value3);
Assert.Equal(45, valueMapped3); 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);
}
} }

View File

@@ -135,19 +135,22 @@ public class Day05 : IDay
{ {
LinesReader reader = new(inputs); LinesReader reader = new(inputs);
Almanac? almanac = Almanac.Parse(reader); Almanac? almanac = Almanac.Parse(reader);
long minLocation = long.MaxValue;
int i = 0; int i = 0;
List<AlmanacRangeMapping> ranges = new();
while (almanac?.Seeds.Count > i) while (almanac?.Seeds.Count > i)
{ {
long seed = almanac?.Seeds[i] ?? 0; long seed = almanac.Seeds[i];
long seedLen = almanac?.Seeds[i + 1] ?? 0; long seedLen = almanac.Seeds[i + 1];
for (long j = 0; j < seedLen; j++) AlmanacRangeMapping range = new() {
{ OriginStart = seed,
long currentSeed = almanac?.ApplyMapping(seed + j) ?? 0; DestinationStart = seed,
if (currentSeed < minLocation) { minLocation = currentSeed; } Length = seedLen,
} };
ranges.Add(range);
i += 2; i += 2;
} }
List<AlmanacRangeMapping> mappedRanges = almanac?.ApplyMapping(ranges) ?? ranges;
long minLocation = mappedRanges.Select(range => range.DestinationStart).Min();
return minLocation.ToString(); return minLocation.ToString();
} }
@@ -176,15 +179,119 @@ public class Day05 : IDay
{ {
public long DestinationStart { get; init; } public long DestinationStart { get; init; }
public long OriginStart { get; init; } public long OriginStart { get; init; }
public long Lenght { get; init; } public long Length { get; init; }
public long? Apply(long value) public long? Apply(long value)
{ {
if (value < OriginStart) { return null; } if (value < OriginStart) { return null; }
long diff = value - OriginStart; long diff = value - OriginStart;
if (diff >= Lenght) { return null; } if (diff >= Length) { return null; }
return DestinationStart + diff; 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 public class AlmanacMapping
@@ -217,7 +324,7 @@ public class Day05 : IDay
AlmanacRangeMapping rangeMapping = new() { AlmanacRangeMapping rangeMapping = new() {
DestinationStart = Convert.ToInt64(mappingParts[0]), DestinationStart = Convert.ToInt64(mappingParts[0]),
OriginStart = Convert.ToInt64(mappingParts[1]), OriginStart = Convert.ToInt64(mappingParts[1]),
Lenght = Convert.ToInt64(mappingParts[2]), Length = Convert.ToInt64(mappingParts[2]),
}; };
mapping.RangeMappings.Add(rangeMapping); mapping.RangeMappings.Add(rangeMapping);
} while (true); } while (true);
@@ -236,6 +343,33 @@ public class Day05 : IDay
} }
return value; return value;
} }
public List<AlmanacRangeMapping> Apply(AlmanacRangeMapping range)
{
List<AlmanacRangeMapping> unMappedRanges = new() {
range,
};
List<AlmanacRangeMapping> newUnMappedRanges = new();
List<AlmanacRangeMapping> 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<AlmanacRangeMapping>();
i++;
}
return mappedRanges.Union(unMappedRanges).ToList();
}
} }
public class Almanac public class Almanac
@@ -277,5 +411,23 @@ public class Day05 : IDay
} }
return value; return value;
} }
public List<AlmanacRangeMapping> ApplyMapping(List<AlmanacRangeMapping> ranges)
{
List<AlmanacRangeMapping> currentRanges = ranges;
int i = 0;
while (i < Mappings.Count)
{
List<AlmanacRangeMapping> 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;
}
} }
} }