diff --git a/AdventOfCode/_2023/Day5.cs b/AdventOfCode/_2023/Day5.cs new file mode 100644 index 0000000..d72ce69 --- /dev/null +++ b/AdventOfCode/_2023/Day5.cs @@ -0,0 +1,258 @@ +using AdventOfCode.Common; +using AdventOfCode.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AdventOfCode._2023 +{ + public class Day5 : AOCDay + { + + private static MapType[] MAP_ORDER = new MapType[] { MapType.SOIL, MapType.FERTILIZER, MapType.WATER, MapType.LIGHT, MapType.TEMPERATURE, MapType.HUMIDITY, MapType.LOCATION }; + protected override AOCResponse ExecutePartA() + { + var inputs = this.SplitInputOnEmptyLine(); + + //Convert our seeds into a long list + var seeds = inputs.First().First().Split(":")[1].Trim().Split(" ").Select(x => long.Parse(x)).ToDictionary(key => key, value => value); + + //Create our map objects + var maps = new List(); + foreach (var map in inputs.Skip(1)) + { + maps.Add(new AlmanacMap(map)); + } + + var prevType = MapType.SEED; + foreach (var mapType in MAP_ORDER) + { + Log($"Mapping from {prevType} to {mapType}"); + var activeMap = maps.First(x => x.From == prevType && x.To == mapType); + foreach (var key in seeds.Keys) + { + seeds[key] = activeMap.Transform(seeds[key]); + Log($"Seed {key} has a value of {seeds[key]} for {mapType}"); + } + prevType = mapType; //Denote our next mapping + } + this.Answer = seeds.Values.Min(); + return this._response; + } + + protected override AOCResponse ExecutePartB() + { + var inputs = this.SplitInputOnEmptyLine(); + + //Create our map objects + var maps = new List(); + foreach (var map in inputs.Skip(1)) + { + maps.Add(new AlmanacMap(map)); + } + + var finalizedSeedRanges = new List(); + var values = inputs.First().First().Split(":")[1].Trim().Split(" ").Select(x => long.Parse(x)).ToArray(); + for (int i = 0; i < values.Length; i += 2) + { + var startSeed = values[i]; + var endSeed = values[i] + values[i + 1]-1; //Minus one because it counts i + + var seed = new SeedRange() { Start = startSeed, End = endSeed }; + + var prevType = MapType.SEED; + var seedRanges = new List() { seed }; + foreach (var mapType in MAP_ORDER) + { + Log($"Mapping from {prevType} to {mapType}"); + var activeMap = maps.First(x => x.From == prevType && x.To == mapType); + seedRanges = activeMap.GetSeedRanges(seedRanges); + prevType = mapType; //Denote our next mapping + } + finalizedSeedRanges = finalizedSeedRanges.Concat(seedRanges).ToList(); + + //var mapType = MapType.SOIL; + //var prevType = MapType.SEED; + //Log($"Mapping from {prevType} to {mapType}"); + //var activeMap = maps.First(x => x.From == prevType && x.To == mapType); + //var postMapSeedRanges = activeMap.GetSeedRanges(seed); + //for (long x = startSeed ; x < endSeed; x++) + //{ + // Console.WriteLine($"Seed {x} has a soil value of {activeMap.Transform(x)}"); + //} + //activeMap.GetSeedRanges(seed); + } + this.Answer = finalizedSeedRanges.Min(x => x.Start); + return this._response; + } + } + + enum MapType + { + SEED, SOIL, FERTILIZER, WATER, LIGHT,TEMPERATURE, HUMIDITY, LOCATION + } + + class AlmanacMap + { + public MapType From { get; set; } + public MapType To { get; set; } + private readonly List _mapData; + public AlmanacMap(string[] maps) + { + var types = maps.First().Split(" ")[0].Split("-to-"); + From = Enum.Parse(types[0].ToUpper()); + To = Enum.Parse(types[1].ToUpper()); + + var mapDataList = new List(); + foreach (var mapData in maps.Skip(1)) + { + var splitMap = mapData.Split(" ").Select(x => long.Parse(x)).ToArray(); + var destinationRange = splitMap[0]; + var sourceRange = splitMap[1]; + var rangeLength = splitMap[2]; + mapDataList.Add(new MapData() + { + Destination = destinationRange, + Source = sourceRange, + Range = rangeLength + }); + } + _mapData = mapDataList.OrderBy(x => x.Source).ToList(); + } + + public long Transform(long value) + { + var result = value; + foreach (var mapData in _mapData) + { + if (value >= mapData.Source && value < (mapData.Source + mapData.Range)) + { + var rangeToBeAdded = value - mapData.Source; + result = mapData.Destination + rangeToBeAdded; + break; + } + } + return result; + } + + public List GetSeedRanges(List ranges) + { + var totalRanges = new List(); + Console.WriteLine("###############"); + foreach (var range in ranges) + { + var seedRanges = new List(); + var originalSeedRanges = new List(); + + + //DEBUG + Console.WriteLine("\n"); + //for (long i = range.Start; i <= range.End; i++) + //{ + // Console.WriteLine($"Seed {i} has value of {Transform(i)}"); + //} + + //Ex) Range 55-67 + foreach (var mapData in _mapData) + { + // Check if any part of rangeA is within rangeB, or vice versa + bool isRangeWithinMapData = (range.Start >= mapData.Source && range.Start <= mapData.SourceEnd) || + (range.End >= mapData.Source && range.End <= mapData.SourceEnd) || + (mapData.Source >= range.Start && mapData.Source <= range.End) || + (mapData.SourceEnd >= range.Start && mapData.SourceEnd <= range.End); + //EX) MapData: 50-98 with a range of 48 and a start of 52 + //if (range.Start >= mapData.Source || range.End <= mapData.SourceEnd) + if (isRangeWithinMapData) + { + var localizedRange = new SeedRange(Math.Max(0, range.Start - mapData.Source), Math.Min(mapData.Range - 1, range.End - mapData.Source) /* - 1 Since we count the start number minus 1 */); + + //For the end range, 92 - 50 = 42 but we do a -1 so why its 41 which is wrong + + var inputStart = mapData.Source + localizedRange.Start; //50 + 5 = 55 + var inputEnd = mapData.Source + localizedRange.End; //50 + 17 = 67 + + originalSeedRanges.Add(new SeedRange() + { + Start = inputStart, + End = inputEnd + }); + + var outputStart = mapData.Destination + localizedRange.Start; //52 + 5 = 57 + var outputEnd = mapData.Destination + localizedRange.End; //52 + 17 = 69 + + seedRanges.Add(new SeedRange() + { + Start = outputStart, + End = outputEnd + }); + } + } + + //For any of the seed ranges created that doesn't cover seed range, create a 1-1 mapping + var validRangesNotFound = GetNonOverlappingRanges(range, originalSeedRanges.OrderBy(x => x.Start).ToList()); + totalRanges = totalRanges.Concat(seedRanges.Concat(validRangesNotFound)).OrderBy(x => x.Start).ToList(); + } + totalRanges.ForEach(x => Console.WriteLine($"Range of: {x.Start} to {x.End}")); + return totalRanges; + } + + private List GetNonOverlappingRanges(SeedRange rangeA, List exclusions) + { + var result = new List(); + + // Add the range before the first exclusion, if any + if (exclusions.Count > 0 && rangeA.Start < exclusions[0].Start) + { + result.Add(new SeedRange(rangeA.Start, Math.Min(rangeA.End, exclusions[0].Start - 1))); + } + + // Add non-overlapping ranges between exclusions + for (int i = 0; i < exclusions.Count - 1; i++) + { + long start = exclusions[i].End + 1; + long end = exclusions[i + 1].Start - 1; + + if (start <= end) + { + result.Add(new SeedRange(start, end)); + } + } + + // Add the range after the last exclusion, if any + if (exclusions.Count > 0 && rangeA.End > exclusions[exclusions.Count - 1].End) + { + result.Add(new SeedRange(Math.Max(rangeA.Start, exclusions[exclusions.Count - 1].End + 1), rangeA.End)); + } + + if (exclusions.Count == 0) + { + result.Add(rangeA); + } + + return result; + } + } + + class SeedRange + { + public SeedRange() { } + public SeedRange(long Start, long End) + { + this.Start = Start; + this.End = End; + } + public long Start { get; set; } + public long End { get; set; } + } + + class MapData + { + public long Destination { get; set; } + public long Source { get; set; } + public long SourceEnd { get { return Source + Range; } } + public long Range { get; set; } + } + + +}