feat: solve part 1 of puzzle 5

This commit is contained in:
Stevan Freeborn
2023-12-05 19:52:23 -06:00
parent d70d1c8bce
commit e4ff5ed4c4
5 changed files with 591 additions and 11 deletions
+140
View File
@@ -0,0 +1,140 @@
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)
{
var result = Almanac.Parse(almanac);
result.Should().BeEquivalentTo(expected);
}
[Theory]
[InlineData(79, 82)]
[InlineData(14, 43)]
[InlineData(55, 86)]
[InlineData(13, 35)]
public void GetSeedLocation_WhenGivenValidSeed_ItShouldReturnExpectedLocation(int seed, int expected)
{
var result = _sut.GetSeedLocation(seed);
result.Should().Be(expected);
}
[Fact]
public void GetLowestSeedLocation_WhenCalled_ItShouldReturnExpectedLocation()
{
var result = _sut.GetLowestSeedLocation();
result.Should().Be(35);
}
[Fact]
public void GetLowestSeedLocation_WhenCalledWithPuzzleInput_ItShouldReturnExpectedLocation()
{
var almanac = File.ReadAllLines("INPUT.txt");
var sut = Almanac.Parse(almanac);
var result = sut.GetLowestSeedLocation();
result.Should().Be(806029445);
}
public static class TestData
{
public static readonly Almanac TestAlmanac =
new(
[
79,
14,
55,
13,
],
[
new(
[
new(2, 98, 50),
new(48, 50, 52),
]
),
new(
[
new(37, 15, 0),
new(2, 52, 37),
new(15, 0, 39),
]
),
new(
[
new(8, 53, 49),
new(42, 11, 0),
new(7, 0, 42),
new(4, 7, 57),
]
),
new(
[
new(7, 18, 88),
new(70, 25, 18),
]
),
new(
[
new(23, 77, 45),
new(19, 45, 81),
new(13, 64, 68),
]
),
new(
[
new(1, 69, 0),
new(69, 0, 1),
]
),
new(
[
new(37, 56, 60),
new(4, 93, 56),
]
),
]
);
public static readonly IEnumerable<object[]> TestAlmanacData =
new List<object[]>
{
new object[]
{
new string[]
{
"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",
},
TestAlmanac,
},
};
}
}
+275
View File
@@ -0,0 +1,275 @@
namespace IYGASAF.Tests;
public class MapTests
{
[Theory]
[MemberData(nameof(TestData.ParseMapData), MemberType = typeof(TestData))]
public void Parse_WhenGivenValidMap_ItShouldReturnExpectedMap(string[] ranges, Map expected)
{
var result = Map.Parse(ranges);
result.Should().BeEquivalentTo(expected);
}
[Theory]
[MemberData(nameof(TestData.ConvertData), MemberType = typeof(TestData))]
public void ConvertSourceToDestination_WhenGivenValidSourceValue_ItShouldReturnExpectedDestinationValue(Map map, int sourceValue, int expected)
{
var result = map.ConvertSourceToDestination(sourceValue);
result.Should().Be(expected);
}
public static class TestData
{
private static readonly Map TestSeedToSoilMap = new(
[
new(2, 98, 50),
new(48, 50, 52),
]
);
private static readonly Map TestSoilToFertilizerMap = new(
[
new(37, 15, 0),
new(2, 52, 37),
new(15, 0, 39),
]
);
private static readonly Map TestFertilizerToWaterMap = new(
[
new(8, 53, 49),
new(42, 11, 0),
new(7, 0, 42),
new(4, 7, 57),
]
);
private static readonly Map TestWaterToLightMap = new(
[
new(7, 18, 88),
new(70, 25, 18),
]
);
private static readonly Map TestLightToTemperatureMap = new(
[
new(23, 77, 45),
new(19, 45, 81),
new(13, 64, 68),
]
);
private static readonly Map TestTemperatureToHumidityMap = new(
[
new(1, 69, 0),
new(69, 0, 1),
]
);
private static readonly Map TestHumidityToLocationMap = new(
[
new(37, 56, 60),
new(4, 93, 56),
]
);
public static IEnumerable<object[]> ConvertData =>
new List<object[]>
{
new object[]
{
TestSeedToSoilMap,
79,
81,
},
new object[]
{
TestSeedToSoilMap,
14,
14,
},
new object[]
{
TestSeedToSoilMap,
55,
57,
},
new object[]
{
TestSeedToSoilMap,
13,
13,
},
new object[]
{
TestSoilToFertilizerMap,
81,
81,
},
new object[]
{
TestSoilToFertilizerMap,
14,
53,
},
new object[]
{
TestSoilToFertilizerMap,
57,
57,
},
new object[]
{
TestSoilToFertilizerMap,
13,
52,
},
new object[]
{
TestFertilizerToWaterMap,
81,
81,
},
new object[]
{
TestFertilizerToWaterMap,
53,
49,
},
new object[]
{
TestFertilizerToWaterMap,
57,
53,
},
new object[]
{
TestFertilizerToWaterMap,
52,
41,
},
new object[]
{
TestWaterToLightMap,
81,
74,
},
new object[]
{
TestWaterToLightMap,
49,
42,
},
new object[]
{
TestWaterToLightMap,
53,
46,
},
new object[]
{
TestWaterToLightMap,
41,
34,
},
new object[]
{
TestLightToTemperatureMap,
74,
78,
},
new object[]
{
TestLightToTemperatureMap,
42,
42,
},
new object[]
{
TestLightToTemperatureMap,
46,
82,
},
new object[]
{
TestLightToTemperatureMap,
34,
34,
},
new object[]
{
TestTemperatureToHumidityMap,
78,
78,
},
new object[]
{
TestTemperatureToHumidityMap,
42,
43,
},
new object[]
{
TestTemperatureToHumidityMap,
82,
82,
},
new object[]
{
TestTemperatureToHumidityMap,
34,
35,
},
new object[]
{
TestHumidityToLocationMap,
78,
82,
},
new object[]
{
TestHumidityToLocationMap,
43,
43,
},
new object[]
{
TestHumidityToLocationMap,
82,
86,
},
new object[]
{
TestHumidityToLocationMap,
35,
35,
},
};
public static IEnumerable<object[]> ParseMapData =>
new List<object[]>
{
new object[]
{
new string[] { "50 98 2", "52 50 48" },
new Map(
[
new(2, 98, 50),
new(48, 50, 52)
]
),
},
new object[]
{
new string[] {"0 15 37", "37 52 2", "39 0 15"},
new Map(
[
new(37, 15, 0),
new(2, 52, 37),
new(15, 0, 39),
]
),
}
};
}
}
+23
View File
@@ -0,0 +1,23 @@
namespace IYGASAF.Tests;
public class RangeTests
{
[Theory]
[MemberData(nameof(TestData.ParseRangeData), MemberType = typeof(TestData))]
public void Parse_WhenGivenValidRange_ItShouldReturnExpectedRange(string range, Range expected)
{
var result = Range.Parse(range);
result.Should().BeEquivalentTo(expected);
}
public static class TestData
{
public static IEnumerable<object[]> ParseRangeData =>
new List<object[]>
{
new object[] { "50 98 2", new Range(2, 98, 50) },
new object[] { "52 50 48", new Range(48, 50, 52) },
};
}
}
-10
View File
@@ -1,10 +0,0 @@
namespace IYGASAF.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
+153 -1
View File
@@ -4,6 +4,158 @@ public class Program
{ {
public static async Task<int> Main(string[] args) public static async Task<int> Main(string[] args)
{ {
return await Task.FromResult(0); if (args.Length is 0)
{
Console.WriteLine("Please provide a path to the input file.");
return -1;
}
if (File.Exists(args[0]) is false)
{
Console.WriteLine("The provided file does not exist.");
return -2;
}
var input = await File.ReadAllLinesAsync(args[0]);
var result = Almanac.Parse(input).GetLowestSeedLocation();
Console.WriteLine($"The lowest seed location is {result}.");
return (int)result;
}
}
public class Almanac(
List<long> seeds,
List<Map> maps
)
{
public List<long> Seeds { get; init; } = seeds;
public List<Map> Maps { get; init; } = maps;
public long GetSeedLocation(long seed) => Maps.Aggregate(
seed,
(currentSeed, map) => map.ConvertSourceToDestination(currentSeed)
);
public long GetLowestSeedLocation() => Seeds
.Select(GetSeedLocation)
.Min();
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 maps = new List<Map>();
var ranges = new List<string>();
for (int i = 1; i < almanac.Length; i++)
{
var currentLine = almanac[i];
if (string.IsNullOrWhiteSpace(currentLine))
{
continue;
}
if (currentLine.Contains(':'))
{
if (ranges.Count is not 0)
{
maps.Add(Map.Parse([.. ranges]));
ranges.Clear();
}
continue;
}
ranges.Add(currentLine);
if (i == almanac.Length - 1)
{
maps.Add(Map.Parse([.. ranges]));
}
}
return new Almanac(
seeds,
maps
);
}
}
public class Map(
List<Range> ranges
)
{
public List<Range> Ranges { get; init; } = ranges;
public static Map Parse(string[] ranges) =>
new(
ranges.Select(Range.Parse).ToList()
);
public Range? GetSourceRange(long sourceValue) =>
Ranges
.Where(range => range.SourceStart <= sourceValue && range.SourceEnd >= sourceValue)
.FirstOrDefault();
public long ConvertSourceToDestination(long valueToConvert)
{
var sourceRange = GetSourceRange(valueToConvert);
if (sourceRange is null)
{
return valueToConvert;
}
var sourceToDestinationOffset = Math.Abs(sourceRange.SourceStart - sourceRange.DestinationStart);
return sourceRange.DestinationStart <= sourceRange.SourceStart
? valueToConvert - sourceToDestinationOffset
: valueToConvert + sourceToDestinationOffset;
}
}
public class Range(
long rangeLength,
long sourceStart,
long destinationStart
)
{
public long RangeLength { get; init; } = rangeLength;
public long SourceStart { get; init; } = sourceStart;
public long SourceEnd => SourceStart + RangeLength - 1;
public long DestinationStart { get; init; } = destinationStart;
public long DestinationEnd => DestinationStart + RangeLength - 1;
public static Range Parse(string range)
{
var parts = range.Split(
' ',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
);
if (parts.Length is not 3 || parts.Any(part => long.TryParse(part, out _) is false))
{
throw new ArgumentException("Range must be in the format of 'DestinationRangeStart:long SourceRangeStart:long RangeLength:long'");
}
var rangeLength = long.Parse(parts[2]);
var sourceStart = long.Parse(parts[1]);
var destinationStart = long.Parse(parts[0]);
return new Range(
rangeLength,
sourceStart,
destinationStart
);
} }
} }