diff --git a/01/Trebuchet.Tests/GlobalUsings.cs b/01/Trebuchet.Tests/GlobalUsings.cs index 2d5b473..d6f0167 100644 --- a/01/Trebuchet.Tests/GlobalUsings.cs +++ b/01/Trebuchet.Tests/GlobalUsings.cs @@ -1,3 +1,3 @@ global using FluentAssertions; -global using Xunit; +global using Xunit; \ No newline at end of file diff --git a/01/Trebuchet/Program.cs b/01/Trebuchet/Program.cs index d90bb99..46e5e1d 100644 --- a/01/Trebuchet/Program.cs +++ b/01/Trebuchet/Program.cs @@ -9,16 +9,16 @@ public class Program if (args.Length is 0) { Console.WriteLine("Please provide a path to the input file."); - return 1; + return -1; } if (File.Exists(args[0]) is false) { Console.WriteLine("The provided file does not exist."); - return 2; + return -2; } - IPuzzleSolver puzzleSolver = args.Length > 1 && args[1] == "2" + IPuzzleSolver puzzleSolver = args.Length > 1 && args[1] == "part2" ? new PartTwoPuzzleSolver() : new PartOnePuzzleSolver(); @@ -27,7 +27,7 @@ public class Program Console.WriteLine($"The sum of all calibration values is {result}."); - return 0; + return result; } } diff --git a/02/CubeConundrum.Tests/GlobalUsings.cs b/02/CubeConundrum.Tests/GlobalUsings.cs index 2d5b473..d6f0167 100644 --- a/02/CubeConundrum.Tests/GlobalUsings.cs +++ b/02/CubeConundrum.Tests/GlobalUsings.cs @@ -1,3 +1,3 @@ global using FluentAssertions; -global using Xunit; +global using Xunit; \ No newline at end of file diff --git a/02/CubeConundrum/Program.cs b/02/CubeConundrum/Program.cs index c9558a4..30fd144 100644 --- a/02/CubeConundrum/Program.cs +++ b/02/CubeConundrum/Program.cs @@ -26,7 +26,14 @@ public class Program ? puzzleSolver.SumGameMinimumPowers(games) : puzzleSolver.SumPossibleGameIds(games); - Console.WriteLine($"The sum of all possible game ids is {result}."); + if (args.Length > 1 && args[1] == "part2") + { + Console.WriteLine($"The sum of the minimum powers of all games is {result}."); + } + else + { + Console.WriteLine($"The sum of all possible game ids is {result}."); + } return result; } diff --git a/03/GearRatios.Tests/GearRatios.Tests.csproj b/03/GearRatios.Tests/GearRatios.Tests.csproj new file mode 100644 index 0000000..d3b6c47 --- /dev/null +++ b/03/GearRatios.Tests/GearRatios.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/03/GearRatios.Tests/GlobalUsings.cs b/03/GearRatios.Tests/GlobalUsings.cs new file mode 100644 index 0000000..2d5b473 --- /dev/null +++ b/03/GearRatios.Tests/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using FluentAssertions; + +global using Xunit; diff --git a/03/GearRatios.Tests/LineTests.cs b/03/GearRatios.Tests/LineTests.cs new file mode 100644 index 0000000..ff23f80 --- /dev/null +++ b/03/GearRatios.Tests/LineTests.cs @@ -0,0 +1,353 @@ +namespace GearRatios.Test; + +public class LineTests +{ + [Theory, MemberData(nameof(TestData.LineData), MemberType = typeof(TestData))] + public void Parse_GivenLine_ItShouldParseExpectedValues(string line, Line expected) + { + var actual = Line.Parse(line); + actual.Should().BeEquivalentTo(expected); + } + + [Theory, MemberData(nameof(TestData.PartNumberData), MemberType = typeof(TestData))] + public void GetPartNumbers_GivenLine_ItShouldReturnExpectedValues(Line currentLine, Line? prevLine, Line? nextLine, List expected) + { + var actual = currentLine.GetPartNumbers(prevLine, nextLine); + actual.Should().BeEquivalentTo(expected); + } + + [Theory, MemberData(nameof(TestData.GearData), MemberType = typeof(TestData))] + public void GetGearRatios_GivenLine_ItShouldReturnExpectedValues(Line currentLine, Line? prevLine, Line? nextLine, List expected) + { + var actual = currentLine.GetGearRatios(prevLine, nextLine); + actual.Should().BeEquivalentTo(expected); + } + + + public static class TestData + { + public static IEnumerable GearData => + new List + { + new object?[] + { + new Line( + [], + [] + ), + null, + null, + new List(), + }, + new object?[] + { + new Line( + [ + new() + { + { 467, new Indexes(0, 2) }, + }, + new() + { + { 114, new Indexes(5, 7) }, + }, + ], + [ + ] + ), + null, + null, + new List(), + }, + new object?[] + { + new Line( + [], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + null, + null, + new List(), + }, + new object?[] + { + new Line( + [ + new() + { + { 617, new Indexes(0, 2) }, + { 114, new Indexes(4, 6) }, + }, + ], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + null, + null, + new List { 617 * 114 }, + }, + new object?[] + { + new Line( + [], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + new Line( + [ + new() + { + { 467, new Indexes(0, 2) }, + }, + new() + { + { 114, new Indexes(5, 7) }, + }, + ], + [] + ), + new Line( + [ + new() + { + { 35, new Indexes(2, 3) }, + }, + new() + { + { 633, new Indexes(6, 8) }, + }, + ], + [] + ), + new List { 467 * 35 }, + } + }; + + public static IEnumerable PartNumberData => + new List + { + new object?[] + { + new Line( + [ + new() + { + { 467, new Indexes(0, 2) }, + }, + new() + { + { 114, new Indexes(5, 7) }, + }, + ], + [ + ] + ), + null, + null, + new List(), + }, + new object?[] + { + new Line( + [], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + null, + null, + new List(), + }, + new object?[] + { + new Line( + [ + new() + { + { 617, new Indexes(0, 2) }, + }, + ], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + null, + null, + new List { 617 }, + }, + new object?[] + { + new Line( + [ + new() + { + { 617, new Indexes(0, 2) }, + }, + new() + { + { 114, new Indexes(6, 8) }, + }, + ], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + new() + { + { "*", new Indexes(9, 9) }, + }, + ] + ), + null, + null, + new List { 617, 114 }, + }, + new object?[] + { + new Line( + [ + new() + { + { 467, new Indexes(0, 2) }, + }, + new() + { + { 114, new Indexes(5, 7) }, + }, + ], + [ + ] + ), + null, + new Line( + [], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + new List() { 467 }, + }, + new object?[] + { + new Line( + [ + new() + { + { 58, new Indexes(7, 8) }, + }, + ], + [ + new() + { + { "+", new Indexes(5, 5) }, + }, + ] + ), + new Line( + [ + new() + { + { 617, new Indexes(0, 2) }, + }, + ], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + new Line( + [ + new() + { + { 592, new Indexes(2, 4) }, + }, + ], + [] + ), + new List(), + }, + }; + + public static IEnumerable LineData => + new List + { + new object[] + { + "467..114..", + new Line( + [ + new() + { + { 467, new Indexes(0, 2) }, + }, + new() + { + { 114, new Indexes(5, 7) }, + }, + ], + [] + ), + }, + new object[] + { + "...*......", + new Line( + [], + [ + new() + { + { "*", new Indexes(3, 3) }, + }, + ] + ), + }, + new object[] + { + "...41+...564", + new Line( + [ + new() + { + { 41, new Indexes(3, 4) }, + }, + new() + { + { 564, new Indexes(9, 11) }, + }, + ], + [ + new() + { + { "+", new Indexes(5, 5) }, + }, + ] + ), + } + }; + }; +} \ No newline at end of file diff --git a/03/GearRatios.Tests/PuzzleSolverTests.cs b/03/GearRatios.Tests/PuzzleSolverTests.cs new file mode 100644 index 0000000..74c000e --- /dev/null +++ b/03/GearRatios.Tests/PuzzleSolverTests.cs @@ -0,0 +1,90 @@ +namespace GearRatios.Test; + +public class PuzzleSolverTests +{ + private readonly PuzzleSolver _sut = new(); + + [Theory] + [MemberData(nameof(TestData.SumPartNumbersData), MemberType = typeof(TestData))] + public void SumPartNumbers_GivenSchematic_ItShouldReturnExpectedValue(string[] schematic, int expected) + { + var actual = _sut.SumPartNumbers(schematic); + actual.Should().Be(expected); + } + + [Theory] + [MemberData(nameof(TestData.SumGearRatios), MemberType = typeof(TestData))] + public void SumGearRatios_GivenSchematic_ItShouldReturnExpectedValue(string[] schematic, int expected) + { + var actual = _sut.SumGearRatios(schematic); + actual.Should().Be(expected); + } + + public static class TestData + { + private static readonly string[] TestSchematic = + [ + "467..114..", + "...*......", + "..35..633.", + "......#...", + "617*......", + ".....+.58.", + "..592.....", + "......755.", + "...$.*....", + ".664.598..", + ]; + + public static IEnumerable SumGearRatios => + new List + { + new object[] + { + TestSchematic, + 467835, + }, + }; + + public static IEnumerable SumPartNumbersData => + new List + { + new object?[] + { + TestSchematic, + 4361, + }, + new object[] + { + new[] + { + "...................305.124................................432..............................................576..313.....514.................", + ".............113...-......&....................&...819...........654..../..........................&901................*....869.257.........", + "...377..&783../.................................9...........855*......940..463................-.........................844.*....@......679.", + "......*...........197.261.....817..336.759............&742......548.......&........748......844.............#.......&........254...169..*...", + }, + 10421, + }, + new object[] + { + new[] + { + ".............................457....834.....................94..564..........&.........498....*...304....../...*.....*..........845.....&...", + ".......12*48.753...244..196....=......$..721....90*29.........*.................+.522.......487...............317.....531..311........20....", + ".177...............*.....*.................*.............*969.611.......*565..338...*.............712..922$.......770......*....522.........", + }, + 8318 + }, + new object[] + { + new[] + { + "663..462...........*..........109..706*.................=.............553........712......*971......674.396...635*..........................", + "........../...728.952...413......*......744.......%..........................300..*....782................*.......742..&424........41+...564", + "........375...%.........*......450.456.$.........714........851.327..#...+......*...+.......179.630....854.................................*", + }, + 11612 + } + }; + } +} \ No newline at end of file diff --git a/03/GearRatios/GearRatios.csproj b/03/GearRatios/GearRatios.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/03/GearRatios/GearRatios.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/03/GearRatios/Program.cs b/03/GearRatios/Program.cs new file mode 100644 index 0000000..baaa1d6 --- /dev/null +++ b/03/GearRatios/Program.cs @@ -0,0 +1,285 @@ +namespace GearRatios; + +public class Program +{ + public async static Task 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 puzzleSolver = new PuzzleSolver(); + + var input = await File.ReadAllLinesAsync(args[0]); + var result = args.Length > 1 && args[1] == "part2" + ? puzzleSolver.SumGearRatios(input) + : puzzleSolver.SumPartNumbers(input); + + if (args.Length > 1 && args[1] == "part2") + { + Console.WriteLine($"The sum of all gear ratios is {result}."); + } + else + { + Console.WriteLine($"The sum of all part numbers is {result}."); + } + + return result; + } +} + +/// +/// Solves the puzzle. +/// +public class PuzzleSolver +{ + /// + /// Sums all part numbers in the schematic. + /// + /// The schematic to parse. + /// The sum of all part numbers. + public int SumPartNumbers(string[] schematic) + { + var allPartNumbers = new List(); + + for (var i = 0; i < schematic.Length; i++) + { + var currentLine = Line.Parse(schematic[i]); + var prevLine = i > 0 ? Line.Parse(schematic[i - 1]) : null; + var nextLine = i < schematic.Length - 1 ? Line.Parse(schematic[i + 1]) : null; + var linePartNumbers = currentLine.GetPartNumbers(prevLine, nextLine); + allPartNumbers.AddRange(linePartNumbers); + } + + return allPartNumbers.Sum(); + } + + /// + /// Sums all gear ratios in the schematic. + /// + /// The schematic to parse. + /// The sum of all gear ratios. + public int SumGearRatios(string[] schematic) + { + var allGearRatios = new List(); + + for (var i = 0; i < schematic.Length; i++) + { + var currentLine = Line.Parse(schematic[i]); + var prevLine = i > 0 ? Line.Parse(schematic[i - 1]) : null; + var nextLine = i < schematic.Length - 1 ? Line.Parse(schematic[i + 1]) : null; + var lineGearRatios = currentLine.GetGearRatios(prevLine, nextLine); + allGearRatios.AddRange(lineGearRatios); + } + + return allGearRatios.Sum(); + } +} + +/// +/// Represents a line in the schematic. +/// +/// The numbers in the line. +/// The special characters in the line. +/// A line in the schematic. +public class Line( + List> numbers, + List> specialCharacters +) +{ + /// + /// Gets the numbers in the line. + /// + public List> Numbers { get; init; } = numbers; + + /// + /// Gets the special characters in the line. + /// + public List> SpecialCharacters { get; init; } = specialCharacters; + + /// + /// Gets the indexes of all special characters in the line. + /// + public List SpecialCharacterIndexes => SpecialCharacters.SelectMany(dict => dict.Values).Select(v => v.Start).ToList(); + + private bool IsGearCharacter(string character) => character == "*"; + + /// + /// Gets the gear ratios in the line. + /// + /// The previous line in the schematic. + /// The next line in the schematic. + /// A list of gear ratios. + public List GetGearRatios(Line? previousLine = null, Line? nextLine = null) + { + var gearRatios = new List(); + var gearCharacters = SpecialCharacters.Where(dict => dict.Keys.Any(IsGearCharacter)).ToList(); + + if (previousLine is not null) + { + Numbers.AddRange(previousLine.Numbers); + } + + if (nextLine is not null) + { + Numbers.AddRange(nextLine.Numbers); + } + + foreach (var gear in gearCharacters) + { + foreach (var (key, value) in gear) + { + var beforeGear = value.Start - 1; + var afterGear = value.End + 1; + + var gearNumbers = Numbers + .Where( + n => n.Values.Any( + v => + v.Start >= beforeGear && v.Start <= afterGear || + v.End >= beforeGear && v.End <= afterGear + ) + ) + .SelectMany(n => n.Keys) + .ToList(); + + if (gearNumbers.Count == 2) + { + gearRatios.Add(gearNumbers.First() * gearNumbers.Last()); + } + } + } + + return gearRatios; + } + + /// + /// Gets the part numbers in the line. + /// + /// The previous line in the schematic. + /// The next line in the schematic. + /// A list of part numbers. + public List GetPartNumbers(Line? previousLine = null, Line? nextLine = null) + { + var partNumbers = new List(); + + if (previousLine is not null) + { + SpecialCharacters.AddRange(previousLine.SpecialCharacters); + } + + if (nextLine is not null) + { + SpecialCharacters.AddRange(nextLine.SpecialCharacters); + } + + foreach (var number in Numbers) + { + foreach (var (key, value) in number) + { + var beforeNumber = value.Start - 1; + var afterNumber = value.End + 1; + + if (SpecialCharacterIndexes.Any(idx => idx >= beforeNumber && idx <= afterNumber)) + { + partNumbers.Add(key); + } + } + } + + return partNumbers; + } + + /// + /// Parses a line in the schematic. + /// + /// The line to parse. + /// An instance of . + public static Line Parse(string line) + { + var numbers = new List>(); + var specialCharacters = new List>(); + + var number = string.Empty; + int? numberStart = null; + int? numberEnd; + + for (var i = 0; i < line.Length; i++) + { + var currentCharacter = line[i]; + + if (char.IsDigit(currentCharacter)) + { + number += line[i]; + numberStart ??= i; + + if (i == line.Length - 1) + { + numberEnd = i; + numbers.Add(new() + { + { + int.Parse(number), + new Indexes(numberStart.Value, numberEnd.Value) + } + }); + } + } + else + { + if (numberStart.HasValue) + { + numberEnd = i - 1; + numbers.Add(new() + { + { + int.Parse(number), + new Indexes(numberStart.Value, numberEnd.Value) + } + }); + number = string.Empty; + numberStart = null; + } + + if (currentCharacter == '.') + { + continue; + } + + specialCharacters.Add(new() + { + { + currentCharacter.ToString(), + new Indexes(i, i) + } + }); + } + } + + return new Line(numbers, specialCharacters); + } +} + +/// +/// Represents the start and end indexes of a number or special character. +/// +public class Indexes(int start, int end) +{ + /// + /// Gets the start index. The start index is the position of the first character in the number or special character. + /// + public int Start { get; init; } = start; + + /// + /// Gets the end index. The end index is the position of the last character in the number or special character. + /// + public int End { get; init; } = end; +} \ No newline at end of file diff --git a/03/SCRATCH.md b/03/SCRATCH.md new file mode 100644 index 0000000..664a93f --- /dev/null +++ b/03/SCRATCH.md @@ -0,0 +1,95 @@ +# Notes + +## Contraints + +- Periods don't count as special characters +- All lines have same length +- If not a number or period then it is a special character + +Each line parsed need to collect all numbers. + +Can't know whether a collected number is a part number until we +have also parsed the proceeding or following line. + +```text +467..114.. +...*...... +..35..633. +......#... +617*...... +``` + +After parsing first line we would have `467` with start index 0 and end index 2 and `114` with start index 5 and end index 7. + +For 467 to be a part number it needs to have a special character at index 3 or it needs to have a special character at index 1, 2, 3, 4 on the next line. + +For 114 to be a part number it needs to have a special character at index 4 or index 8 or it needs to have a special character at index 4, 5, 6, 7 on the next line. + +Need to keep track of: + +- Previous line - will start as empty +- Current line +- Next line + +So you look at the current line and you parse all the numbers with their start and end index. You also parse all special characters with their index. + +Then you end up with something like this: currentLine + +```json +{ + "numbers": [ + "467": { + "start": 0, + "end": 2 + }, + "114": { + "start": 5, + "end": 7 + } + ], + "specialCharacters": [] +} +``` + +```csharp +var currentLineString = "467..114.."; +var currentLine = new Line(currentLineString); + +class Line +{ + public List> Numbers { get; set; } + public List> SpecialCharacters { get; set; } + + public Line(string line) + { + // parse line + } +} + +public class Indexes +{ + public int Start { get; set; } + public int End { get; set; } + + public Indexes(int start, int length) + { + Start = start; + End = start + length; + } +} +``` + +Then you see if those numbers are part numbers just by checking if a special character proceeds or follows their start and end indexes. + +```csharp + +``` + +If not then you need to see if there is a previous line and whether there is a special character at on the previous line that has an index that is between 1 less than the start index and 1 more than the end index. + +If not then you need to see if there is a next line and whether there is a special character at on the next line that has an index that is between 1 less than the start index and 1 more than the end index. + +Basic approach: + +- Parse the current, previous, and next lines into a model that keeps track of the numbers and special characters and their indexes for that line. +- Then each line should be able to evaluate what is a part number or not. Or what is a gear or not given its previous and next lines. diff --git a/AdventOfCode2023.sln b/AdventOfCode2023.sln index f685422..0d08297 100644 --- a/AdventOfCode2023.sln +++ b/AdventOfCode2023.sln @@ -15,6 +15,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CubeConundrum", "02\CubeCon EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CubeConundrum.Tests", "02\CubeConundrum.Tests\CubeConundrum.Tests.csproj", "{4825CE42-4717-4DA6-944F-9BD9A825932B}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03", "03", "{EA04152D-316A-4AA4-9BC1-57F6A9F3CD4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearRatios", "03\GearRatios\GearRatios.csproj", "{F697AC19-C92D-4E2A-975A-1A16A1961133}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearRatios.Tests", "03\GearRatios.Tests\GearRatios.Tests.csproj", "{FEE9253C-A56B-4735-BDB3-C62220C5B1B1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,11 +46,21 @@ Global {4825CE42-4717-4DA6-944F-9BD9A825932B}.Debug|Any CPU.Build.0 = Debug|Any CPU {4825CE42-4717-4DA6-944F-9BD9A825932B}.Release|Any CPU.ActiveCfg = Release|Any CPU {4825CE42-4717-4DA6-944F-9BD9A825932B}.Release|Any CPU.Build.0 = Release|Any CPU + {F697AC19-C92D-4E2A-975A-1A16A1961133}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F697AC19-C92D-4E2A-975A-1A16A1961133}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F697AC19-C92D-4E2A-975A-1A16A1961133}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F697AC19-C92D-4E2A-975A-1A16A1961133}.Release|Any CPU.Build.0 = Release|Any CPU + {FEE9253C-A56B-4735-BDB3-C62220C5B1B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FEE9253C-A56B-4735-BDB3-C62220C5B1B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FEE9253C-A56B-4735-BDB3-C62220C5B1B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FEE9253C-A56B-4735-BDB3-C62220C5B1B1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {3F8EAC09-4BC7-43AA-B72B-48DDD2710F6D} = {8C29858C-623A-461A-BF0B-254E151CD9C2} {4A5226E4-5B90-4D6C-9224-EBE3F04470ED} = {8C29858C-623A-461A-BF0B-254E151CD9C2} {95D6522F-4893-4A66-9B39-7F0268E290B8} = {4FD81B09-302F-4C76-BECB-B4559DF3984F} {4825CE42-4717-4DA6-944F-9BD9A825932B} = {4FD81B09-302F-4C76-BECB-B4559DF3984F} + {F697AC19-C92D-4E2A-975A-1A16A1961133} = {EA04152D-316A-4AA4-9BC1-57F6A9F3CD4E} + {FEE9253C-A56B-4735-BDB3-C62220C5B1B1} = {EA04152D-316A-4AA4-9BC1-57F6A9F3CD4E} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 0a6aab1..f44df71 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,19 @@ To run the solutions, navigate to the solution folder and run the following comm dotnet run -- ``` +Or for part 2 solutions + +```bash +dotnet run -- part2 +``` + ## Challenges | Day | Problem | Solution | Status | Notes | | --- | -------------------------- | :-----------------------------: | :----: | ------------------------------------------------------------------- | | 01 | [Problem](./01/PROBLEM.md) | [Solution](./01/Trebuchet/) | ✅ | The trickiest part here was accounting for overlapping digit words. | | 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/) | ⌛ | +| 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/) | ⌛ | | 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/) | ⌛ | | 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/) | ⌛ |