using Microsoft.Data.Sqlite; namespace AOC.Tests.Y2023; [TestFixture] [Parallelizable(ParallelScope.All)] public class Day05 { [SetUp] public void Setup() { realData = File.ReadAllLines(Path.Combine(TestContext.CurrentContext.TestDirectory, "Y2023", "Data", $"{GetThisClassName()}.dat")); } protected string GetThisClassName() { return GetType().Name; } private string[] realData; private record Map { public long DestinationRangeStart { get; set; } public long SourceRangeStart { get; set; } public long RangeLength { get; set; } } private record Almanac { public List SeedsToBePlanted { get; set; } public List SeedToSoilMap { get; set; } public List SoilToFertiliserMap { get; set; } public List FertiliserToWaterMap { get; set; } public List WaterToLightMap { get; set; } public List LightToTemperatureMap { get; set; } public List TemperatureToHumidityMap { get; set; } public List HumidityToLocationMap { get; set; } } private List GetMap(string[] lines, string mapName) { List map = new(); int lineNumber = 0; while (lines[lineNumber] != mapName) { lineNumber++; } lineNumber++; while (lineNumber < lines.Length && lines[lineNumber] != "") { string[] mapLine = lines[lineNumber].Split(" "); map.Add(new Map { DestinationRangeStart = long.Parse(mapLine[0]), SourceRangeStart = long.Parse(mapLine[1]), RangeLength = long.Parse(mapLine[2]) }); lineNumber++; } return map; } private long GetValueUsingMaps(List maps, long sourceValue) { // is the seed number within any of the ranges for the seed to soil map? if (maps.Any(m => m.SourceRangeStart + m.RangeLength >= sourceValue && sourceValue >= m.SourceRangeStart)) { Map appropriateMap = maps .OrderBy(m => m.DestinationRangeStart) .First(m => m.SourceRangeStart + m.RangeLength >= sourceValue && sourceValue >= m.SourceRangeStart); // Calculate how far to move from the start of the source range to our seed number long increment = sourceValue - appropriateMap.SourceRangeStart; return appropriateMap.DestinationRangeStart + increment; } // Just use the same value as the source return sourceValue; } [TestCase(@"seeds: 79 14 55 13 seed-to-soil map: 50 98 2 52 50 48 soil-to-fertilizer map: 0 15 37 37 52 2 39 0 15 fertilizer-to-water map: 49 53 8 0 11 42 42 0 7 57 7 4 water-to-light map: 88 18 7 18 25 70 light-to-temperature map: 45 77 23 81 45 19 68 64 13 temperature-to-humidity map: 0 69 1 1 0 69 humidity-to-location map: 60 56 37 56 93 4", 35)] [TestCase(null, 265018614)] // The actual answer public void Part1(string input, int? expected) { string[] lines = input != null ? input.Split("\n") : realData; Almanac almanac = new(); // Get the seeds from the first line almanac.SeedsToBePlanted = lines[0].Split(": ")[1].Split(" ").Select(long.Parse).ToList(); // Get the maps when we don't know what line number they occur on almanac.SeedToSoilMap = GetMap(lines, "seed-to-soil map:"); almanac.SoilToFertiliserMap = GetMap(lines, "soil-to-fertilizer map:"); almanac.FertiliserToWaterMap = GetMap(lines, "fertilizer-to-water map:"); almanac.WaterToLightMap = GetMap(lines, "water-to-light map:"); almanac.LightToTemperatureMap = GetMap(lines, "light-to-temperature map:"); almanac.TemperatureToHumidityMap = GetMap(lines, "temperature-to-humidity map:"); almanac.HumidityToLocationMap = GetMap(lines, "humidity-to-location map:"); List locations = new(); foreach (long seed in almanac.SeedsToBePlanted) { // Get the soil, fertilizer, water, light, temperature, humidity, and location long soil = GetValueUsingMaps(almanac.SeedToSoilMap, seed); long fertiliser = GetValueUsingMaps(almanac.SoilToFertiliserMap, soil); long water = GetValueUsingMaps(almanac.FertiliserToWaterMap, fertiliser); long light = GetValueUsingMaps(almanac.WaterToLightMap, water); long temperature = GetValueUsingMaps(almanac.LightToTemperatureMap, light); long humidity = GetValueUsingMaps(almanac.TemperatureToHumidityMap, temperature); long location = GetValueUsingMaps(almanac.HumidityToLocationMap, humidity); Console.WriteLine($"Seed {seed} grows in location {location}"); locations.Add(location); } long result = locations.Min(); if (expected != null) { Assert.That(result, Is.EqualTo(expected.Value)); } Console.WriteLine($"Part 1: {result}"); } [TestCase(@"seeds: 79 14 55 13 seed-to-soil map: 50 98 2 52 50 48 soil-to-fertilizer map: 0 15 37 37 52 2 39 0 15 fertilizer-to-water map: 49 53 8 0 11 42 42 0 7 57 7 4 water-to-light map: 88 18 7 18 25 70 light-to-temperature map: 45 77 23 81 45 19 68 64 13 temperature-to-humidity map: 0 69 1 1 0 69 humidity-to-location map: 60 56 37 56 93 4", 46)] [TestCase(null, 1783)] // The actual answer public void Part2(string input, int? expected) { string[] lines = input != null ? input.Split("\n") : realData; Almanac almanac = new(); // Get the maps when we don't know what line number they occur on almanac.SeedToSoilMap = GetMap(lines, "seed-to-soil map:"); almanac.SoilToFertiliserMap = GetMap(lines, "soil-to-fertilizer map:"); almanac.FertiliserToWaterMap = GetMap(lines, "fertilizer-to-water map:"); almanac.WaterToLightMap = GetMap(lines, "water-to-light map:"); almanac.LightToTemperatureMap = GetMap(lines, "light-to-temperature map:"); almanac.TemperatureToHumidityMap = GetMap(lines, "temperature-to-humidity map:"); almanac.HumidityToLocationMap = GetMap(lines, "humidity-to-location map:"); List locations = new(); List values = lines[0].Split(": ")[1].Split(" ").Select(long.Parse).ToList(); for (int i = 0; i < values.Count; i += 2) { long startRange = values[i]; long rangeLength = values[i + 1]; for (long seed = startRange; seed < startRange + rangeLength; seed++) { // Get the soil, fertilizer, water, light, temperature, humidity, and location long soil = GetValueUsingMaps(almanac.SeedToSoilMap, seed); long fertiliser = GetValueUsingMaps(almanac.SoilToFertiliserMap, soil); long water = GetValueUsingMaps(almanac.FertiliserToWaterMap, fertiliser); long light = GetValueUsingMaps(almanac.WaterToLightMap, water); long temperature = GetValueUsingMaps(almanac.LightToTemperatureMap, light); long humidity = GetValueUsingMaps(almanac.TemperatureToHumidityMap, temperature); long location = GetValueUsingMaps(almanac.HumidityToLocationMap, humidity); // Console.WriteLine( // $"Seed {seed} corresponds to soil {soil}, fertiliser {fertiliser}, water {water}, light {light}, temperature {temperature}, humidity {humidity}, and location {location}"); locations.Add(location); } } long result = locations.Min(); if (expected != null) { Assert.That(result, Is.EqualTo(expected.Value)); } Console.WriteLine($"Part 2: {result}"); } }