using AdventOfCode.Core; using AdventOfCode.Core.Shared.Grid; namespace AdventOfCode.Solutions._2023 { public class Day05(InputReader reader) : IChallange { private readonly InputReader _inputReader = reader; public const string SeedSoil = "seed-to-soil map:"; public const string SoilFertilizer = "soil-to-fertilizer map:"; public const string FertilizerWater = "fertilizer-to-water map:"; public const string WaterLight = "water-to-light map:"; public const string LightTemperature = "light-to-temperature map:"; public const string TemperatureHumidity = "temperature-to-humidity map:"; public const string HumidityLocation = "humidity-to-location map:"; public async Task GetSolutionPart1() { List data = new(await _inputReader.ReadAsArrayString()); SeedLocationMapper[] seeds = data[0].Split(' ').Skip(1).Select(s => new SeedLocationMapper { Seed = long.Parse(s) }).ToArray(); // creating maps int stringSeperator = data.IndexOf(SeedSoil) + 1; List soil = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(SoilFertilizer) + 1; List fertilizer = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(FertilizerWater) + 1; List water = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(WaterLight) + 1; List light = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(LightTemperature) + 1; List temperature = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(TemperatureHumidity) + 1; List humidity = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(HumidityLocation) + 1; List location = MapData(data, stringSeperator, data.Count); foreach (SeedLocationMapper seed in seeds) { seed.Soil = GetTargetLocation(soil, seed.Seed); seed.Fertilizer = GetTargetLocation(fertilizer, seed.Soil); seed.Water = GetTargetLocation(water, seed.Fertilizer); seed.Light = GetTargetLocation(light, seed.Water); seed.Temperature = GetTargetLocation(temperature, seed.Light); seed.Humidity = GetTargetLocation(humidity, seed.Temperature); seed.Location = GetTargetLocation(location, seed.Humidity); } return seeds.Min(seed => seed.Location).ToString(); } public async Task GetSolutionPart2() { List data = new(await _inputReader.ReadAsArrayString()); int stringSeperator = data.IndexOf(SeedSoil) + 1; List soil = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(SoilFertilizer) + 1; List fertilizer = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(FertilizerWater) + 1; List water = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(WaterLight) + 1; List light = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(LightTemperature) + 1; List temperature = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(TemperatureHumidity) + 1; List humidity = MapData(data, stringSeperator, data.IndexOf(string.Empty, stringSeperator)); stringSeperator = data.IndexOf(HumidityLocation) + 1; List location = MapData(data, stringSeperator, data.Count); long[] seedData = data[0].Split(' ').Skip(1).Select(long.Parse).ToArray(); List ranges = []; for(int index = 0; index < seedData.Length / 2; index++) { Line[] seedRange = [new Line(seedData[index * 2], 0, seedData[(index * 2) + 1])]; Line[] soilMapped = GetTargetLocation(soil, seedRange); Line[] fertilizerMapped = GetTargetLocation(fertilizer, soilMapped); Line[] waterMapped = GetTargetLocation(water, fertilizerMapped); Line[] lightMapped = GetTargetLocation(light, waterMapped); Line[] temperatureMapped = GetTargetLocation(temperature, lightMapped); Line[] humidityMapped = GetTargetLocation(humidity, temperatureMapped); Line[] locationMapped = GetTargetLocation(location, humidityMapped); ranges.Add(locationMapped.OrderBy(l => l.Start.X).First()); } return ranges.OrderBy(l => l.Start.X).First().Start.X.ToString(); } private static List MapData(List data, int startIndex, int endIndex) { return data .Skip(startIndex) .Take(endIndex - startIndex) .Select(line => line .Split(' ') .Select(long.Parse) .ToArray()) .Select(values => new InOutMapper ( destinationStart: values[0], sourceStart: values[1], range: values[2] )) .ToList(); } private static long GetTargetLocation(List mapping, long location) { InOutMapper? mapper = mapping.FirstOrDefault(mapping => mapping.Intersect(location)); return mapper != null ? location + mapper.Delta : location; } private static Line[] GetTargetLocation(List mapping, Line[] seedRange) { List resultRanges = []; for (int seedRangeIndex = 0; seedRangeIndex < seedRange.Length; seedRangeIndex++) { Line currentSeedRange = seedRange[seedRangeIndex]; InOutMapper[] mappers = [.. mapping.Where(mapping => mapping.Intersect(currentSeedRange)).OrderBy(map => map.SourceStart)]; if (mappers.Length == 0) { // I do not expect to hit this but just to be sure. resultRanges.Add(currentSeedRange); continue; } long lastId = currentSeedRange.Start.X; foreach(InOutMapper mapper in mappers.OrderBy(m => m.Start.X)) { //List mapperResults = []; if (lastId < mapper.SourceStart) { // there is some uneffected space infront of the mapper resultRanges.Add(new Line(lastId, 0, mapper.SourceStart - lastId)); lastId = mapper.SourceStart; } long end = mapper.End.X > currentSeedRange.End.X ? currentSeedRange.End.X : mapper.End.X; Line map = new(lastId, 0, end - lastId + 1); map.Start.X += mapper.Delta; map.End.X += mapper.Delta; resultRanges.Add(map); lastId = end + 1; } if (lastId < currentSeedRange.End.X) { Line map = new(new Point(lastId, 0, ""), currentSeedRange.End); resultRanges.Add(map); } } // validate if (seedRange.Sum(r => r.Length) != resultRanges.Sum(r => r.Length)) { Console.WriteLine("Lost seeds!"); } return [.. resultRanges]; } private class SeedLocationMapper { public long Seed { get; set; } public long Soil { get; set; } public long Fertilizer { get; set; } public long Water { get; set; } public long Light { get; set; } public long Temperature { get; set; } public long Humidity { get; set; } public long Location { get; set; } } private class InOutMapper : Line { public long SourceStart => base.Start.X; public long DestinationStart { get; set; } public long Range => base.Length; public long Delta => DestinationStart - SourceStart; public bool Intersect(long value) => base.Intersect(new Point(value, 0)); public InOutMapper(long sourceStart, long destinationStart, long range) : base(sourceStart, 0, range) { DestinationStart = destinationStart; } public override string ToString() { return $"Dest start: {DestinationStart}, Source start: {SourceStart}, Range: {Range}"; } } } }