From b596be7353a15cbf3cedbc861c2d786e1b001210 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:19:25 +0000 Subject: [PATCH] feat(*): Add array searching helper methods --- .../AOC.Tests/Helpers/ArraySearching.cs | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 AdventOfCode C#/AOC.Tests/Helpers/ArraySearching.cs diff --git a/AdventOfCode C#/AOC.Tests/Helpers/ArraySearching.cs b/AdventOfCode C#/AOC.Tests/Helpers/ArraySearching.cs new file mode 100644 index 0000000..41cc3fc --- /dev/null +++ b/AdventOfCode C#/AOC.Tests/Helpers/ArraySearching.cs @@ -0,0 +1,200 @@ +namespace AOC.Tests.Helpers; + +/// +/// Provides utility methods for searching within 2D character arrays. +/// +public static class ArraySearching +{ + // Directions: Right, Left, Down, Up, Diagonal Down-Right, Diagonal Down-Left, Diagonal Up-Right, Diagonal Up-Left + private static readonly (int RowOffset, int ColOffset)[] Directions = new[] + { + (0, 1), // Right + (0, -1), // Left + (1, 0), // Down + (-1, 0), // Up + (1, 1), // Diagonal down-right + (1, -1), // Diagonal down-left + (-1, 1), // Diagonal up-right + (-1, -1) // Diagonal up-left + }; + + public static char[,] ConvertListTo2DArray(List list) + { + int rows = list.Count; + int cols = list[0].Length; + char[,] grid = new char[rows, cols]; + for (int r = 0; r < rows; r++) + { + string row = list[r]; + for (int c = 0; c < cols; c++) + { + grid[r, c] = row[c]; + } + } + + return grid; + } + + public static void Print2DCharArray(char[,] array) + { + for (int i = 0; i < array.GetLength(0); i++) // Iterate rows + { + for (int j = 0; j < array.GetLength(1); j++) // Iterate columns + { + Console.Write(array[i, j] + " "); + } + + Console.WriteLine(); // New line after each row + } + } + + /// + /// Finds all occurrences of the specified target string within the given 2D character array. + /// Occurrences are searched in all eight directions (horizontal, vertical, and diagonals). + /// Returns a collection of matches, where each match is represented as a collection of coordinates. + /// + /// The 2D character array to search. + /// The string to find. + /// + /// A list of matches, where each match is a list of (row, column) tuples indicating the positions of the found + /// string. + /// + /// Thrown if grid or target is null. + /// Thrown if target is empty. + public static List> FindStringOccurrences(char[,] grid, string target) + { + if (grid == null) + { + throw new ArgumentNullException(nameof(grid)); + } + + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (target.Length == 0) + { + throw new ArgumentException("Target string cannot be empty.", nameof(target)); + } + + List>? matches = new(); + int rows = grid.GetLength(0); + int cols = grid.GetLength(1); + char firstChar = target[0]; + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + // Optimization: Only start search if first char matches + if (grid[r, c] == firstChar) + { + foreach ((int rowOffset, int colOffset) in Directions) + { + if (CanFit(grid, r, c, rowOffset, colOffset, target.Length)) + { + List<(int, int)>? coords = TryMatch(grid, r, c, rowOffset, colOffset, target); + if (coords != null) + { + matches.Add(coords); + } + } + } + } + } + } + + return matches; + } + + /// + /// Extracts all integer values from a 2D character array. + /// Consecutive digits are combined to form a number. Non-digit characters separate numbers. + /// Returns a list of all parsed integers. + /// + /// The 2D character array to search. + /// A list of integers found in the grid. + /// Thrown if grid is null. + public static List ExtractNumbers(char[,] grid) + { + if (grid == null) + { + throw new ArgumentNullException(nameof(grid)); + } + + List? numbers = new(); + int rows = grid.GetLength(0); + int cols = grid.GetLength(1); + + // We'll parse row-by-row for simplicity + for (int r = 0; r < rows; r++) + { + int currentNumber = 0; + bool buildingNumber = false; + + for (int c = 0; c < cols; c++) + { + char ch = grid[r, c]; + if (char.IsDigit(ch)) + { + buildingNumber = true; + currentNumber = currentNumber * 10 + (ch - '0'); + } + else + { + if (buildingNumber) + { + numbers.Add(currentNumber); + currentNumber = 0; + buildingNumber = false; + } + } + } + + // If the row ended but we were still building a number + if (buildingNumber) + { + numbers.Add(currentNumber); + } + } + + return numbers; + } + + /// + /// Checks if the target string can fit starting at position (r, c) in the given direction. + /// + private static bool CanFit(char[,] grid, int r, int c, int rowOffset, int colOffset, int length) + { + int rows = grid.GetLength(0); + int cols = grid.GetLength(1); + + int endRow = r + (length - 1) * rowOffset; + int endCol = c + (length - 1) * colOffset; + + return endRow >= 0 && endRow < rows && endCol >= 0 && endCol < cols; + } + + /// + /// Attempts to match the target string starting at (r, c) in a given direction. + /// If matched, returns the list of coordinates. Otherwise, returns null. + /// + private static List<(int, int)> TryMatch(char[,] grid, int r, int c, int rowOffset, int colOffset, string target) + { + List<(int, int)>? coords = new(target.Length); + for (int i = 0; i < target.Length; i++) + { + int rr = r + i * rowOffset; + int cc = c + i * colOffset; + if (grid[rr, cc] != target[i]) + { + return null; + } + + coords.Add((rr, cc)); + } + + return coords; + } +}