Merge pull request #5 from StevanFreeborn/stevanfreeborn/feat/solve-puzzle-5

stevanfreeborn/feat/solve puzzle 5
This commit is contained in:
Stevan Freeborn
2023-12-06 14:47:12 -06:00
committed by GitHub
10 changed files with 921 additions and 2 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
global using FluentAssertions;
global using Xunit;
global using Xunit;
+195
View File
@@ -0,0 +1,195 @@
namespace IYGASAF.Tests;
public class AlmanacTests
{
[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 sut = TestData.TestAlmanac;
var result = sut.GetSeedLocation(seed);
result.Should().Be(expected);
}
[Fact]
public void GetLowestSeedLocation_WhenCalled_ItShouldReturnExpectedLocation()
{
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()
{
var almanac = File.ReadAllLines("INPUT.txt");
var sut = Almanac.Parse(almanac);
var result = sut.GetLowestSeedLocation();
result.Should().Be(806029445);
}
[Fact]
public void ParseSeedsAsRanges_WhenGivenValidSeedsString_ItShouldReturnExpectedSeeds()
{
var seedsString = "79 14 55 13";
var expected = new List<SeedRange>
{
new(79, 14),
new(55, 13),
};
var result = Almanac.ParseSeedsAsRanges(seedsString);
result.Should().BeEquivalentTo(expected);
}
public static class TestData
{
public static readonly Almanac TestAlmanac =
new(
[
79,
14,
55,
13,
],
[
new(79, 14),
new(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 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<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,
},
};
}
}
+3
View File
@@ -0,0 +1,3 @@
global using FluentAssertions;
global using Xunit;
+36
View File
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IYGASAF\IYGASAF.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\INPUT.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
+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
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+361
View File
@@ -0,0 +1,361 @@
using System.Diagnostics;
namespace IYGASAF;
public class Program
{
public static async Task<int> Main(string[] args)
{
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 seedsAreRanges = args.Length > 1 && args[1] == "part2";
Console.WriteLine("Parsing almanac...");
var almanac = Almanac.Parse(input);
Console.WriteLine("Getting lowest seed location...");
var stopwatch = new Stopwatch();
stopwatch.Start();
var result = almanac.GetLowestSeedLocation(seedsAreRanges);
stopwatch.Stop();
Console.WriteLine($"The lowest seed location is {result}. ({stopwatch.ElapsedMilliseconds}ms)");
return (int)result;
}
}
/// <summary>
/// Represents an almanac.
/// </summary>
/// <param name="seeds">The seeds.</param>
/// <param name="seedRanges">The seed ranges.</param>
/// <param name="maps">The maps.</param>
/// <returns>An instance of <see cref="Almanac"/>.</returns>
public class Almanac(
List<long> seeds,
List<SeedRange> seedRanges,
List<Map> maps
)
{
/// <summary>
/// Gets the seed ranges.
/// </summary>
public List<SeedRange> SeedRanges { get; init; } = seedRanges;
/// <summary>
/// Gets the seeds.
/// </summary>
public List<long> Seeds { get; init; } = seeds;
/// <summary>
/// Gets the maps.
/// </summary>
public List<Map> Maps { get; init; } = maps;
private static List<long> GetSeedsFromString(string seedsString) =>
seedsString
.Split(
' ',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
.Select(long.Parse)
.ToList();
/// <summary>
/// Gets the seed location.
/// </summary>
/// <param name="seed">The seed.</param>
/// <returns>The seed location.</returns>
public long GetSeedLocation(long seed) => Maps.Aggregate(
seed,
(currentSeed, map) => map.ConvertSourceToDestination(currentSeed)
);
private class LocationResult
{
public long Value { get; set; } = long.MaxValue;
}
/// <summary>
/// Gets the lowest seed location.
/// </summary>
/// <param name="seedsAreRanges">If set to true seeds are treated as ranges</param>
/// <returns>The lowest seed location.</returns>
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;
}
}
}
return lowestLocation.Value;
}
/// <summary>
/// Parses the seeds as ranges.
/// </summary>
/// <param name="seedsString">The seeds string.</param>
/// <returns>The seeds as ranges.</returns>
public static List<SeedRange> ParseSeedsAsRanges(string seedsString)
{
var seeds = new List<SeedRange>();
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;
}
/// <summary>
/// Parses the specified almanac.
/// </summary>
/// <param name="almanac">The almanac.</param>
/// <returns>An instance of <see cref="Almanac"/>.</returns>
public static Almanac Parse(string[] almanac)
{
var seedsString = almanac[0].Split(':')[1];
var seeds = GetSeedsFromString(seedsString);
var seedRanges = ParseSeedsAsRanges(seedsString);
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,
seedRanges,
maps
);
}
}
/// <summary>
/// Represents a map.
/// </summary>
/// <param name="ranges">The ranges contained within the map</param>
/// <returns>An instance of <see cref="Map"/>.</returns>
public class Map(
List<Range> ranges
)
{
/// <summary>
/// Gets the ranges.
/// </summary>
public List<Range> Ranges { get; init; } = [.. ranges.OrderBy(range => range.SourceStart)];
/// <summary>
/// Parses a map from the specified ranges.
/// </summary>
/// <param name="ranges">The ranges.</param>
/// <returns>An instance of <see cref="Map"/>.</returns>
public static Map Parse(string[] ranges) =>
new(
ranges.Select(Range.Parse).ToList()
);
/// <summary>
/// Gets the source range.
/// </summary>
/// <param name="sourceValue">The source value.</param>
/// <returns>The source <see cref="Range"/>.</returns>
public Range? GetSourceRange(long sourceValue) =>
Ranges
.Where(range => range.SourceStart <= sourceValue && range.SourceEnd >= sourceValue)
.FirstOrDefault();
/// <summary>
/// Converts the source to destination.
/// </summary>
/// <param name="valueToConvert">The value to convert.</param>
/// <returns>The converted value.</returns>
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;
}
}
/// <summary>
/// Represents a seed range.
/// </summary>
/// <param name="start">The start.</param>
/// <param name="length">The length.</param>
/// <returns>An instance of <see cref="SeedRange"/>.</returns>
public class SeedRange(
long start,
long length
)
{
/// <summary>
/// Gets the start of the range. It is inclusive.
/// </summary>
public long Start { get; init; } = start;
/// <summary>
/// Gets the length of the range.
/// </summary>
public long Length { get; init; } = length;
/// <summary>
/// Gets the end of the range. It is inclusive.
/// </summary>
public long End => Start + Length - 1;
}
/// <summary>
/// Represents a map range.
/// </summary>
/// <param name="rangeLength">Length of the range.</param>
/// <param name="sourceStart">The source start.</param>
/// <param name="destinationStart">The destination start.</param>
/// <returns>An instance of <see cref="Range"/>.</returns>
public class Range(
long rangeLength,
long sourceStart,
long destinationStart
)
{
/// <summary>
/// Gets the length of the range.
/// </summary>
public long RangeLength { get; init; } = rangeLength;
/// <summary>
/// Gets the start of the source range. It is inclusive.
/// </summary>
public long SourceStart { get; init; } = sourceStart;
/// <summary>
/// Gets the end of the source range. It is inclusive.
/// </summary>
public long SourceEnd => SourceStart + RangeLength - 1;
/// <summary>
/// Gets the start of the destination range. It is inclusive.
/// </summary>
public long DestinationStart { get; init; } = destinationStart;
/// <summary>
/// Gets the end of the destination range. It is inclusive.
/// </summary>
public long DestinationEnd => DestinationStart + RangeLength - 1;
/// <summary>
/// Parses the specified range.
/// </summary>
/// <param name="range">The range.</param>
/// <returns>An instance of <see cref="Range"/>.</returns>
/// <exception cref="ArgumentException">Range must be in the format of 'DestinationRangeStart:long SourceRangeStart:long RangeLength:long'</exception>
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
);
}
}
+16
View File
@@ -27,6 +27,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scratchcards", "04\Scratchc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scratchcards.Tests", "04\Scratchcards.Tests\Scratchcards.Tests.csproj", "{FF8A5512-85FD-45E7-9F97-EC528465949C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "05", "05", "{5067FD6B-8F03-4502-AB83-8A391D801B47}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IYGASAF", "05\IYGASAF\IYGASAF.csproj", "{417E0408-31C8-4945-B684-5004233D6AF4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IYGASAF.Tests", "05\IYGASAF.Tests\IYGASAF.Tests.csproj", "{CB90004D-9420-4411-9110-B50274537FE5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +74,14 @@ Global
{FF8A5512-85FD-45E7-9F97-EC528465949C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF8A5512-85FD-45E7-9F97-EC528465949C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF8A5512-85FD-45E7-9F97-EC528465949C}.Release|Any CPU.Build.0 = Release|Any CPU
{417E0408-31C8-4945-B684-5004233D6AF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{417E0408-31C8-4945-B684-5004233D6AF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{417E0408-31C8-4945-B684-5004233D6AF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{417E0408-31C8-4945-B684-5004233D6AF4}.Release|Any CPU.Build.0 = Release|Any CPU
{CB90004D-9420-4411-9110-B50274537FE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB90004D-9420-4411-9110-B50274537FE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB90004D-9420-4411-9110-B50274537FE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB90004D-9420-4411-9110-B50274537FE5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3F8EAC09-4BC7-43AA-B72B-48DDD2710F6D} = {8C29858C-623A-461A-BF0B-254E151CD9C2}
@@ -78,5 +92,7 @@ Global
{FEE9253C-A56B-4735-BDB3-C62220C5B1B1} = {EA04152D-316A-4AA4-9BC1-57F6A9F3CD4E}
{B58CF14B-97A1-45F6-967F-29FA82DAFD0F} = {522D6D1E-54AD-4479-BB78-0AB5D7493EFC}
{FF8A5512-85FD-45E7-9F97-EC528465949C} = {522D6D1E-54AD-4479-BB78-0AB5D7493EFC}
{417E0408-31C8-4945-B684-5004233D6AF4} = {5067FD6B-8F03-4502-AB83-8A391D801B47}
{CB90004D-9420-4411-9110-B50274537FE5} = {5067FD6B-8F03-4502-AB83-8A391D801B47}
EndGlobalSection
EndGlobal
+1 -1
View File
@@ -41,7 +41,7 @@ dotnet run -- <path-to-input-file> part2
| 02 | [Problem](./02/PROBLEM.md) | [Solution](./02/CubeConundrum/) | ✅ | The key to me here was to parse the input into a useful model. |
| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/GearRatios/) | ✅ | The edge case that got me here was lines ending with a part number. |
| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/Scratchcards/) | ✅ | Part 2 gets out of hand quickly with just 200 cards. |
| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/) | |
| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/IYGASAF/) | | I brute forced part 2 using parallelism. I know shame. |
| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/) | ⌛ |
| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/) | ⌛ |
| 08 | [Problem](./08/PROBLEM.md) | [Solution](./08/) | ⌛ |