From f068ba8b7b257771a45838395951ccfc598c7f8e Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:39:37 +0000 Subject: [PATCH] feat(2023-03): Finish day 3 --- AdventOfCode/AOC.Tests/Y2023/Day03.cs | 601 +++++++++++++++----------- 1 file changed, 346 insertions(+), 255 deletions(-) diff --git a/AdventOfCode/AOC.Tests/Y2023/Day03.cs b/AdventOfCode/AOC.Tests/Y2023/Day03.cs index 087c09b..3e6d12f 100644 --- a/AdventOfCode/AOC.Tests/Y2023/Day03.cs +++ b/AdventOfCode/AOC.Tests/Y2023/Day03.cs @@ -1,227 +1,319 @@ -using System.Reflection; +namespace AOC.Tests.Y2023; -namespace AOC.Tests.Y2023 +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class Day03 { - [TestFixture, Parallelizable(ParallelScope.All)] - public class Day03 + [SetUp] + public void Setup() { - protected string GetThisClassName() { return this.GetType().Name; } - private string[] realData; + realData = File.ReadAllLines(Path.Combine(TestContext.CurrentContext.TestDirectory, "Y2023", "Data", + $"{GetThisClassName()}.dat")); + } - [SetUp] - public void Setup() - { - realData = File.ReadAllLines(Path.Combine(TestContext.CurrentContext.TestDirectory, "Y2023", "Data", $"{GetThisClassName()}.dat")); - } + protected string GetThisClassName() { return GetType().Name; } + private string[] realData; - private bool CheckSurroundingCellsForSymbols(char[,] grid, List<(int, int)> coords, bool isTesting = false) + private bool CheckSurroundingCellsForSymbols(char[,] grid, List<(int, int)> coords, bool isTesting = false) + { + char[,] grid2 = new char[grid.GetLength(0), grid.GetLength(1)]; + foreach ((int, int) coord in coords) { - char[,] grid2 = new char[grid.GetLength(0), grid.GetLength(1)]; - foreach (var coord in coords) + int x = coord.Item1; + int y = coord.Item2; + + grid2[x, y] = grid[x, y]; + + for (int i = x - 1; i <= x + 1; i++) { - int x = coord.Item1; - int y = coord.Item2; - - grid2[x, y] = grid[x, y]; - - for (int i = x - 1; i <= x + 1; i++) + for (int j = y - 1; j <= y + 1; j++) { - for (int j = y - 1; j <= y + 1; j++) + if (i >= 0 && i < grid.GetLength(0) && j >= 0 && j < grid.GetLength(1) && !coords.Contains((i, j))) { - if (i >= 0 && i < grid.GetLength(0) && j >= 0 && j < grid.GetLength(1) && !coords.Contains((i, j))) + if (isTesting) + { + // Console.WriteLine($"{i},{j}"); + grid2[i, j] = 'b'; + } + + char cell = grid[i, j]; + if (!char.IsDigit(cell) && cell != '.') { if (isTesting) { - // Console.WriteLine($"{i},{j}"); - grid2[i, j] = 'b'; + grid2[i, j] = 'a'; } - - char cell = grid[i, j]; - if (!char.IsDigit(cell) && cell != '.') + else { - if (isTesting) - { - grid2[i, j] = 'a'; - } - else - { - return true; - } + return true; } } } } } - - if (isTesting) - { - // Print out the grid2 - for (int i = 0; i < grid2.GetLength(0); i++) - { - string line = ""; - for (int j = 0; j < grid2.GetLength(1); j++) - { - line += grid2[i, j] != '\u0000' ? grid2[i, j] : '.'; - } - Console.WriteLine(line); - } - } - return false; } - private List GetAllValidPartNumbers(string[] lines) + if (isTesting) { - // Create a 2D array to represent the grid. - int rows = lines.Length; - int cols = lines[0].Length; - char[,] grid = new char[rows, cols]; - - // Populate the grid - for (int i = 0; i < rows; i++) + // Print out the grid2 + for (int i = 0; i < grid2.GetLength(0); i++) { - string line = lines[i]; - for (int j = 0; j < cols; j++) + string line = ""; + for (int j = 0; j < grid2.GetLength(1); j++) { - grid[i, j] = line[j]; + line += grid2[i, j] != '\u0000' ? grid2[i, j] : '.'; } + + Console.WriteLine(line); } - - List numbers = new(); - - // Iterate over each cell in the grid. - for (int i = 0; i < rows; i++) - { - Console.WriteLine($"Line {i+1}"); - for (int j = 0; j < cols; j++) - { - List<(int, int)> numberCoords = new(); - - // If the cell contains a digit and it's the start of a number (either the left cell is not a digit or it's the left boundary), start collecting the digits to form a number until you reach a cell that's not a digit. - if (char.IsDigit(grid[i, j]) && (j == 0 || !char.IsDigit(grid[i, j - 1]))) - { - // Collect the digits to form a number until you reach a cell that's not a digit. - string number = ""; - - while (char.IsDigit(grid[i, j])) - { - number += grid[i, j]; - numberCoords.Add((i, j)); - j++; - if (j >= cols) - { - break; - } - } - - // If the number is valid, add it to a list. - if (number.Length >= 1) - { - int formattedNumber = int.Parse(number); - - Console.WriteLine(formattedNumber); - - // Check the eight surrounding cells of each digit in the number. - if (numberCoords.Count > 0) - { - bool isValidPartNumber = CheckSurroundingCellsForSymbols(grid, numberCoords, false); - // If any of the surrounding cells contain a symbol, add the number to a list. - if (isValidPartNumber) - { - Console.WriteLine("Valid part number"); - numbers.Add(formattedNumber); - } - } - } - } - } - } - - // Return the list of numbers. - return numbers; } - - public bool CheckSurroundingCellsForNumbers(char[,] grid, List<(int, int)> coords, bool isTesting = false) + + return false; + } + + private List GetAllValidPartNumbers(string[] lines) + { + // Create a 2D array to represent the grid. + int rows = lines.Length; + int cols = lines[0].Length; + char[,] grid = new char[rows, cols]; + + // Populate the grid + for (int i = 0; i < rows; i++) { - foreach (var gearCoord in coords) + string line = lines[i]; + for (int j = 0; j < cols; j++) + { + grid[i, j] = line[j]; + } + } + + List numbers = new(); + + // Iterate over each cell in the grid. + for (int i = 0; i < rows; i++) + { + Console.WriteLine($"Line {i + 1}"); + for (int j = 0; j < cols; j++) { List<(int, int)> numberCoords = new(); - int x = gearCoord.Item1; - int y = gearCoord.Item2; - - // Check the eight surrounding cells of each digit in the number. - for (int i = x - 1; i <= x + 1; i++) + + // If the cell contains a digit and it's the start of a number (either the left cell is not a digit or it's the left boundary), start collecting the digits to form a number until you reach a cell that's not a digit. + if (char.IsDigit(grid[i, j]) && (j == 0 || !char.IsDigit(grid[i, j - 1]))) { - for (int j = y - 1; j <= y + 1; j++) + // Collect the digits to form a number until you reach a cell that's not a digit. + string number = ""; + + while (char.IsDigit(grid[i, j])) { - if (i >= 0 && i < grid.GetLength(0) && j >= 0 && j < grid.GetLength(1) && (i != x || j != y)) + number += grid[i, j]; + numberCoords.Add((i, j)); + j++; + if (j >= cols) { - if (char.IsDigit(grid[i, j])) + break; + } + } + + // If the number is valid, add it to a list. + if (number.Length >= 1) + { + int formattedNumber = int.Parse(number); + + Console.WriteLine(formattedNumber); + + // Check the eight surrounding cells of each digit in the number. + if (numberCoords.Count > 0) + { + bool isValidPartNumber = CheckSurroundingCellsForSymbols(grid, numberCoords); + // If any of the surrounding cells contain a symbol, add the number to a list. + if (isValidPartNumber) { - numberCoords.Add((i, j)); + Console.WriteLine("Valid part number"); + numbers.Add(formattedNumber); } } } } - - if (numberCoords.Count == 2) - { - int number1 = int.Parse(grid[numberCoords[0].Item1, numberCoords[0].Item2].ToString()); - int number2 = int.Parse(grid[numberCoords[1].Item1, numberCoords[1].Item2].ToString()); - int gearRatio = number1 * number2; - // gearRatios.Add(gearRatio); - } } - return false; } - - private List GetAllGearRatios(string[] lines) - { - // Create a 2D array to represent the grid. - int rows = lines.Length; - int cols = lines[0].Length; - char[,] grid = new char[rows, cols]; + // Return the list of numbers. + return numbers; + } - // Populate the grid - for (int i = 0; i < rows; i++) + private int CountNumbers(List<(int, int)> numberCoords) + { + // Group coordinates by the X-axis and sort each group by the Y-axis + IEnumerable> groupedCoords = numberCoords.GroupBy(coord => coord.Item1) + .Select(group => group.OrderBy(coord => coord.Item2)); + + int numberCount = 0; + foreach (IOrderedEnumerable<(int, int)> group in groupedCoords) + { + int lastY = int.MinValue; + foreach ((int, int) coord in group) { - string line = lines[i]; - for (int j = 0; j < cols; j++) + // If there is a gap in the Y-axis, increment the number count + if (coord.Item2 - lastY > 1) { - grid[i, j] = line[j]; + numberCount++; } + + lastY = coord.Item2; + } + } + + return numberCount; + } + + private List ExtractNumbers(List<(int, int)> numberCoords, char[,] grid) + { + // Initialize a list to hold the extracted numbers + List numbers = new(); + + // Sort the coordinates by Y-axis (Item2) then by X-axis (Item1) + List<(int, int)> sortedCoords = + numberCoords.OrderBy(coord => coord.Item2).ThenBy(coord => coord.Item1).ToList(); + + // Create a HashSet to keep track of processed coordinates + HashSet<(int, int)> processedCoords = new(); + + foreach ((int x, int y) in sortedCoords) + { + // Skip this coordinate if it has already been processed + if (processedCoords.Contains((x, y))) + { + continue; } - List gearRatios = new(); - List<(int, int)> gearCoords = new(); - - // Iterate over each cell in the grid to find all gears - for (int i = 0; i < rows; i++) + // Start from the current coordinate and scan to the left until a non-digit is found or it reaches the beginning of the row + int startX = x; + while (startX > 0 && char.IsDigit(grid[startX - 1, y])) { - Console.WriteLine($"Line {i+1}"); - for (int j = 0; j < cols; j++) - { - + startX--; + } - if (grid[i, j] == '*') + // Build the number by scanning to the right from the startX position + string currentNumberStr = ""; + int currentX = startX; + while (currentX < grid.GetLength(0) && char.IsDigit(grid[currentX, y])) + { + currentNumberStr += grid[currentX, y]; + // Mark the coordinate as processed + processedCoords.Add((currentX, y)); + currentX++; + } + + // If a number is formed, add it to the list + if (currentNumberStr.Length > 0) + { + numbers.Add(int.Parse(currentNumberStr)); + } + } + + // Return the list of extracted numbers + return numbers; + } + + + private int GetGearRatioFromExactlyTwoNumbersInSurroundingCells(char[,] grid, (int, int) gearCoord) + { + List<(int, int)> numberCoords = new(); + int x = gearCoord.Item1; + int y = gearCoord.Item2; + int gearRatio = 0; + + // Check the eight surrounding cells of the gear + for (int i = x - 1; i <= x + 1; i++) + { + for (int j = y - 1; j <= y + 1; j++) + { + if (i >= 0 && i < grid.GetLength(0) && j >= 0 && j < grid.GetLength(1) && (i != x || j != y)) + { + if (char.IsDigit(grid[i, j])) { - gearCoords.Add((i, j)); + numberCoords.Add((i, j)); } } } - - // Now find all instances where exactly two numbers are adjacent to a gear - - - // Return the list of numbers. - return gearRatios; } - - - [TestCase("blah", 1)] - public void TestCheckSurroundingCellsForSymbolsFunctionWorksCorrectly(string input, int? expected) + + // Consider each set of cells in the same x axis with a digit to their immediate right as a single number + foreach ((int, int) numberCoord in numberCoords) { - string exampleSchematic = @"467..114.. + Console.WriteLine($"{numberCoord.Item1},{numberCoord.Item2}"); + } + + Console.WriteLine("---"); + + List numbers = ExtractNumbers(numberCoords, grid); + + foreach (int number in numbers) + { + Console.WriteLine($"Number: {number}"); + } + + Console.WriteLine("==="); + + if (numbers.Count == 2) + { + // If there are exactly two numbers around the gear, generate the gear ratio + gearRatio = numbers[0] * numbers[1]; + Console.WriteLine($"{numbers[0]} * {numbers[1]} = {gearRatio}"); + } + + return gearRatio; + } + + + private int GetSumOfAllGearRatios(string[] lines) + { + // Create a 2D array to represent the grid. + int cols = lines[0].Length; + int rows = lines.Length; + char[,] grid = new char[cols, rows]; + + // Populate the grid + for (int j = 0; j < rows; j++) + { + string line = lines[j]; + for (int i = 0; i < cols; i++) + { + grid[i, j] = line[i]; + } + } + + List<(int, int)> gearCoords = new(); + + // Iterate over each cell in the grid to find all gears + for (int i = 0; i < cols; i++) + { + // Console.WriteLine($"Line {i + 1}"); + for (int j = 0; j < rows; j++) + { + if (grid[i, j] == '*') + { + gearCoords.Add((i, j)); + } + } + } + + int gearRatios = 0; + // Now find all instances where exactly two numbers are adjacent to a gear and sum their gear ratios + foreach ((int, int) gear in gearCoords) + { + gearRatios += GetGearRatioFromExactlyTwoNumbersInSurroundingCells(grid, gear); + } + + return gearRatios; + } + + + [TestCase("blah", 1)] + public void TestCheckSurroundingCellsForSymbolsFunctionWorksCorrectly(string input, int? expected) + { + string exampleSchematic = @"467..114.. ...*...... ..35..633. ......#... @@ -231,63 +323,63 @@ namespace AOC.Tests.Y2023 ......755. ...$.*.... .664.598.."; - string[] lines = exampleSchematic.Split("\n"); - int rows = lines.Length; - int cols = lines[0].Length; - char[,] grid = new char[rows, cols]; + string[] lines = exampleSchematic.Split("\n"); + int rows = lines.Length; + int cols = lines[0].Length; + char[,] grid = new char[rows, cols]; - for (int i = 0; i < rows; i++) + for (int i = 0; i < rows; i++) + { + string line = lines[i]; + for (int j = 0; j < cols; j++) { - string line = lines[i]; - for (int j = 0; j < cols; j++) - { - grid[i, j] = line[j]; - } + grid[i, j] = line[j]; } - - List<(int, int)> coords = new() - { - (0,0), - (0,1), - (0,2), - (0,5), - (0,6), - (0,7), - (2,2), - (2,3), - (2,6), - (2,7), - (2,8), - (4,0), - (4,1), - (4,2), - (5,7), - (5,8), - (6,2), - (6,3), - (6,4), - (7,6), - (7,7), - (7,8), - (9,1), - (9,2), - (9,3), - (9,5), - (9,6), - (9,7), - }; - - CheckSurroundingCellsForSymbols(grid, coords, true); - - //if (expected != null) - //{ - // Assert.That(result, Is.EqualTo(expected.Value)); - //} - - //Console.WriteLine($"Part 2: {result}"); } - [TestCase(@"467..114.. + List<(int, int)> coords = new() + { + (0, 0), + (0, 1), + (0, 2), + (0, 5), + (0, 6), + (0, 7), + (2, 2), + (2, 3), + (2, 6), + (2, 7), + (2, 8), + (4, 0), + (4, 1), + (4, 2), + (5, 7), + (5, 8), + (6, 2), + (6, 3), + (6, 4), + (7, 6), + (7, 7), + (7, 8), + (9, 1), + (9, 2), + (9, 3), + (9, 5), + (9, 6), + (9, 7) + }; + + CheckSurroundingCellsForSymbols(grid, coords, true); + + //if (expected != null) + //{ + // Assert.That(result, Is.EqualTo(expected.Value)); + //} + + //Console.WriteLine($"Part 2: {result}"); + } + + [TestCase(@"467..114.. ...*...... ..35..633. ......#... @@ -297,26 +389,26 @@ namespace AOC.Tests.Y2023 ......755. ...$.*.... .664.598..", 4361)] - [TestCase(null, 550934)] // The actual answer - public void Part1(string input, int? expected) + [TestCase(null, 550934)] // The actual answer + public void Part1(string input, int? expected) + { + string[] lines = input != null ? input.Split("\n") : realData; + + // any number adjacent to a symbol, even diagonally, is a "part number" and should be included in your sum. (Periods (.) do not count as a symbol.) + List validPartNumbers = GetAllValidPartNumbers(lines); + + // The result is the sum of all the valid part numbers + int result = validPartNumbers.Sum(); + + if (expected != null) { - string[] lines = input != null ? input.Split("\n") : realData; - - // any number adjacent to a symbol, even diagonally, is a "part number" and should be included in your sum. (Periods (.) do not count as a symbol.) - List validPartNumbers = GetAllValidPartNumbers(lines); - - // The result is the sum of all the valid part numbers - int result = validPartNumbers.Sum(); - - if (expected != null) - { - Assert.That(result, Is.EqualTo(expected.Value)); - } - - Console.WriteLine($"Part 1: {result}"); + Assert.That(result, Is.EqualTo(expected.Value)); } - [TestCase(@"467..114.. + Console.WriteLine($"Part 1: {result}"); + } + + [TestCase(@"467..114.. ...*...... ..35..633. ......#... @@ -326,23 +418,22 @@ namespace AOC.Tests.Y2023 ......755. ...$.*.... .664.598..", 467835)] - [TestCase(null, 1783)] // The actual answer - public void Part2(string input, int? expected) + [TestCase(null, 1783)] // The actual answer + public void Part2(string input, int? expected) + { + string[] lines = input != null ? input.Split("\n") : realData; + + // A gear is any * symbol that is adjacent to exactly two part numbers. Its gear ratio is the result of multiplying those two numbers together. + int gearRatiosSum = GetSumOfAllGearRatios(lines); + + // The result is the sum of all the gear ratios + int result = gearRatiosSum; + + if (expected != null) { - string[] lines = input != null ? input.Split("\n") : realData; - - // A gear is any * symbol that is adjacent to exactly two part numbers. Its gear ratio is the result of multiplying those two numbers together. - List gearRatios = GetAllGearRatios(lines); - - // The result is the sum of all the gear ratios - int result = gearRatios.Sum(); - - if (expected != null) - { - Assert.That(result, Is.EqualTo(expected.Value)); - } - - Console.WriteLine($"Part 2: {result}"); + Assert.That(result, Is.EqualTo(expected.Value)); } + + Console.WriteLine($"Part 2: {result}"); } -} \ No newline at end of file +}