diff --git a/05/IYGASAF.Tests/AlmanacTests.cs b/05/IYGASAF.Tests/AlmanacTests.cs index 9d6b07c..80e113b 100644 --- a/05/IYGASAF.Tests/AlmanacTests.cs +++ b/05/IYGASAF.Tests/AlmanacTests.cs @@ -2,8 +2,6 @@ namespace IYGASAF.Tests; public class AlmanacTests { - private readonly Almanac _sut = TestData.TestAlmanac; - [Theory] [MemberData(nameof(TestData.TestAlmanacData), MemberType = typeof(TestData))] public void Parse_WhenGivenValidAlmanac_ItShouldReturnExpectedAlmanac(string[] almanac, Almanac expected) @@ -19,17 +17,27 @@ public class AlmanacTests [InlineData(13, 35)] public void GetSeedLocation_WhenGivenValidSeed_ItShouldReturnExpectedLocation(int seed, int expected) { - var result = _sut.GetSeedLocation(seed); + var sut = TestData.TestAlmanac; + var result = sut.GetSeedLocation(seed); result.Should().Be(expected); } [Fact] public void GetLowestSeedLocation_WhenCalled_ItShouldReturnExpectedLocation() { - var result = _sut.GetLowestSeedLocation(); + var sut = TestData.TestAlmanac; + var result = sut.GetLowestSeedLocation(); result.Should().Be(35); } + [Fact] + public void GetLowestSeedLocation_WhenCalledAndSeedsAreParsedAsRanges_ItShouldReturnExpectedLocation() + { + var sut = Almanac.Parse(TestData.TestAlmanacInput); + var result = sut.GetLowestSeedLocation(true); + result.Should().Be(46); + } + [Fact] public void GetLowestSeedLocation_WhenCalledWithPuzzleInput_ItShouldReturnExpectedLocation() { @@ -39,6 +47,19 @@ public class AlmanacTests result.Should().Be(806029445); } + [Fact] + public void ParseSeedsAsRanges_WhenGivenValidSeedsString_ItShouldReturnExpectedSeeds() + { + var seedsString = "79 14 55 13"; + var expected = new List + { + new(79, 14), + new(55, 13), + }; + var result = Almanac.ParseSeedsAsRanges(seedsString); + result.Should().BeEquivalentTo(expected); + } + public static class TestData { public static readonly Almanac TestAlmanac = @@ -49,6 +70,10 @@ public class AlmanacTests 55, 13, ], + [ + new(79, 14), + new(55, 13), + ], [ new( [ @@ -99,6 +124,36 @@ public class AlmanacTests ] ); + public static readonly string[] TestAlmanacInput = + [ + "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", + ]; + public static readonly IEnumerable TestAlmanacData = new List { diff --git a/05/IYGASAF/Program.cs b/05/IYGASAF/Program.cs index f121d98..e6691ab 100644 --- a/05/IYGASAF/Program.cs +++ b/05/IYGASAF/Program.cs @@ -18,7 +18,13 @@ public class Program var input = await File.ReadAllLinesAsync(args[0]); - var result = Almanac.Parse(input).GetLowestSeedLocation(); + var seedsAreRanges = args.Length > 1 && args[1] == "part2"; + + Console.WriteLine("Parsing almanac..."); + var almanac = Almanac.Parse(input); + + Console.WriteLine("Getting lowest seed location..."); + var result = almanac.GetLowestSeedLocation(seedsAreRanges); Console.WriteLine($"The lowest seed location is {result}."); @@ -28,31 +34,99 @@ public class Program public class Almanac( List seeds, + List seedRanges, List maps ) { + public List SeedRanges { get; init; } = seedRanges; public List Seeds { get; init; } = seeds; public List Maps { get; init; } = maps; + private static List GetSeedsFromString(string seedsString) => + seedsString + .Split( + ' ', + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries + ) + .Select(long.Parse) + .ToList(); + public long GetSeedLocation(long seed) => Maps.Aggregate( seed, (currentSeed, map) => map.ConvertSourceToDestination(currentSeed) ); - public long GetLowestSeedLocation() => Seeds - .Select(GetSeedLocation) - .Min(); + public class LocationResult + { + public long Value { get; set; } = long.MaxValue; + } + + public long GetLowestSeedLocation(bool seedsAreRanges = false) + { + var lowestLocation = new LocationResult(); + + if (seedsAreRanges) + { + Parallel.ForEach(SeedRanges, (currentSeedRange) => + { + var currentSeedRangeStart = currentSeedRange.Start; + var currentSeedRangeEnd = currentSeedRange.End; + + for (long j = currentSeedRangeStart; j <= currentSeedRangeEnd; j++) + { + var currentSeed = j; + var currentSeedLocation = GetSeedLocation(currentSeed); + + lock (lowestLocation) + { + if (currentSeedLocation < lowestLocation.Value) + { + lowestLocation.Value = currentSeedLocation; + } + } + } + }); + } + else + { + for (int i = 0; i < Seeds.Count; i++) + { + var currentSeed = Seeds[i]; + var currentSeedLocation = GetSeedLocation(currentSeed); + + if (currentSeedLocation < lowestLocation.Value) + { + lowestLocation.Value = currentSeedLocation; + } + } + } + + File.WriteAllText("output.txt", lowestLocation.Value.ToString()); + + return lowestLocation.Value; + } + + public static List ParseSeedsAsRanges(string seedsString) + { + var seeds = new List(); + var seedNumbers = GetSeedsFromString(seedsString); + + for (int i = 0; i < seedNumbers.Count; i += 2) + { + var start = seedNumbers[i]; + var length = seedNumbers[i + 1]; + + seeds.Add(new(start, length)); + } + + return seeds; + } public static Almanac Parse(string[] almanac) { var seedsString = almanac[0].Split(':')[1]; - var seeds = seedsString - .Split( - ' ', - StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries - ) - .Select(long.Parse) - .ToList(); + var seeds = GetSeedsFromString(seedsString); + var seedRanges = ParseSeedsAsRanges(seedsString); var maps = new List(); var ranges = new List(); @@ -86,6 +160,7 @@ public class Almanac( return new Almanac( seeds, + seedRanges, maps ); } @@ -95,7 +170,7 @@ public class Map( List ranges ) { - public List Ranges { get; init; } = ranges; + public List Ranges { get; init; } = [.. ranges.OrderBy(range => range.SourceStart)]; public static Map Parse(string[] ranges) => new( @@ -124,6 +199,16 @@ public class Map( } } +public class SeedRange( + long start, + long length +) +{ + public long Start { get; init; } = start; + public long Length { get; init; } = length; + public long End => Start + Length - 1; +} + public class Range( long rangeLength, long sourceStart,