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/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.Test/GlobalUsings.cs b/03/GearRatios.Test/GlobalUsings.cs deleted file mode 100644 index 8c927eb..0000000 --- a/03/GearRatios.Test/GlobalUsings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Xunit; \ No newline at end of file diff --git a/03/GearRatios.Test/UnitTest1.cs b/03/GearRatios.Test/UnitTest1.cs deleted file mode 100644 index 71a183c..0000000 --- a/03/GearRatios.Test/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GearRatios.Test; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file diff --git a/03/GearRatios.Test/GearRatios.Test.csproj b/03/GearRatios.Tests/GearRatios.Tests.csproj similarity index 93% rename from 03/GearRatios.Test/GearRatios.Test.csproj rename to 03/GearRatios.Tests/GearRatios.Tests.csproj index d0b5d1d..d3b6c47 100644 --- a/03/GearRatios.Test/GearRatios.Test.csproj +++ b/03/GearRatios.Tests/GearRatios.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/03/GearRatios.Tests/GlobalUsings.cs b/03/GearRatios.Tests/GlobalUsings.cs new file mode 100644 index 0000000..7fef4b0 --- /dev/null +++ b/03/GearRatios.Tests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions; \ No newline at end of file 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/Program.cs b/03/GearRatios/Program.cs index 3751555..5596c24 100644 --- a/03/GearRatios/Program.cs +++ b/03/GearRatios/Program.cs @@ -1,2 +1,228 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +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; + } +} + +public class PuzzleSolver +{ + 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(); + } + + 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(); + } +} + +public class Line( + List> numbers, + List> specialCharacters +) +{ + public List> Numbers { get; init; } = numbers; + public List> SpecialCharacters { get; init; } = specialCharacters; + public List SpecialCharacterIndexes => SpecialCharacters.SelectMany(dict => dict.Values).Select(v => v.Start).ToList(); + + private bool IsGearCharacter(string character) => character == "*"; + + 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; + } + + 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; + } + + 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); + } +} + +public class Indexes(int start, int end) +{ + public int Start { get; init; } = start; + 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..0c65a13 --- /dev/null +++ b/03/SCRATCH.md @@ -0,0 +1,90 @@ +# 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. +``` diff --git a/AdventOfCode2023.sln b/AdventOfCode2023.sln index b864a8b..0d08297 100644 --- a/AdventOfCode2023.sln +++ b/AdventOfCode2023.sln @@ -19,7 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03", "03", "{EA04152D-316A- 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.Test", "03\GearRatios.Test\GearRatios.Test.csproj", "{FEE9253C-A56B-4735-BDB3-C62220C5B1B1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearRatios.Tests", "03\GearRatios.Tests\GearRatios.Tests.csproj", "{FEE9253C-A56B-4735-BDB3-C62220C5B1B1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution