diff --git a/04/CeresSearch.Tests/GridTests.cs b/04/CeresSearch.Tests/GridTests.cs new file mode 100644 index 0000000..44e19d2 --- /dev/null +++ b/04/CeresSearch.Tests/GridTests.cs @@ -0,0 +1,56 @@ +namespace CeresSearch.Tests; + +public class GridTests +{ + private readonly string[] _exampleInput = [ + "MMMSXXMASM", + "MSAMXMSMSA", + "AMXSXMAAMM", + "MSAMASMSMX", + "XMASAMXAMM", + "XXAMMXXAMA", + "SMSMSASXSS", + "SAXAMASAAA", + "MAMMMXMMMM", + "MXMXAXMASX", + ]; + + private async Task GetPuzzleInput() + { + return await File.ReadAllLinesAsync(Path.Combine(AppContext.BaseDirectory, "INPUT.txt")); + } + + [Test] + public async Task CountOccurrences_WhenCalledWithExampleInput_ItShouldReturnExpectedCount() + { + var result = new Grid(_exampleInput).CountOccurrences("XMAS"); + + await Assert.That(result).IsEqualTo(18); + } + + [Test] + public async Task CountOccurrences_WhenCalledWithPuzzleInput_ItShouldReturnExpectedCount() + { + var input = await GetPuzzleInput(); + var result = new Grid(input).CountOccurrences("XMAS"); + + await Assert.That(result).IsEqualTo(2644); + } + + [Test] + public async Task CountXmases_WhenCalledWithExampleInput_ItShouldReturnExpectedCount() + { + var result = new Grid(_exampleInput).CountXmases(); + + await Assert.That(result).IsEqualTo(9); + } + + [Test] + public async Task CountXmases_WhenCalledWithPuzzleInput_ItShouldReturnExpectedCount() + { + var input = await GetPuzzleInput(); + var result = new Grid(input).CountXmases(); + + await Assert.That(result).IsEqualTo(1952); + } +} \ No newline at end of file diff --git a/04/CeresSearch/Grid.cs b/04/CeresSearch/Grid.cs new file mode 100644 index 0000000..3cb7f10 --- /dev/null +++ b/04/CeresSearch/Grid.cs @@ -0,0 +1,154 @@ +using System.Xml; + +namespace CeresSearch; + +record Direction(int XOffset, int YOffset) +{ + public static readonly Direction Up = new(0, -1); + public static readonly Direction Down = new(0, 1); + public static readonly Direction Left = new(-1, 0); + public static readonly Direction Right = new(1, 0); + public static readonly Direction DownToTheRight = new(1, 1); + public static readonly Direction DownToTheLeft = new(-1, 1); + public static readonly Direction UpToTheRight = new(1, -1); + public static readonly Direction UpToTheLeft = new(-1, -1); +} + +class Grid(string[] input) +{ + private readonly Direction[] _directions = [ + Direction.UpToTheLeft, + Direction.UpToTheRight, + Direction.DownToTheLeft, + Direction.DownToTheRight, + Direction.Up, + Direction.Down, + Direction.Left, + Direction.Right, + ]; + + public int CountXmases() + { + var total = 0; + + WalkGrid((rowIndex, columnIndex) => + { + if (FoundXmas(rowIndex, columnIndex)) + { + total++; + } + }); + + return total; + } + + public int CountOccurrences(string word) + { + var total = 0; + + WalkGrid((rowIndex, columnIndex) => + total += _directions.Count(direction => FoundWord(word, direction, rowIndex, columnIndex)) + ); + + return total; + } + + private void WalkGrid(Action callback) + { + for (var rowIndex = 0; rowIndex < input.Length; rowIndex++) + { + var row = input[rowIndex]; + + for (var columnIndex = 0; columnIndex < row.Length; columnIndex++) + { + callback(rowIndex, columnIndex); + } + } + } + + private bool FoundXmas(int rowIndex, int colIndex) + { + var currentCharacter = input[rowIndex][colIndex]; + + if (currentCharacter is not 'A') + { + return false; + } + + var xmasMap = new Dictionary(); + + foreach (var direction in _directions) + { + if ( + direction == Direction.Up || + direction == Direction.Down || + direction == Direction.Left || + direction == Direction.Right + ) + { + continue; + } + + var rowIndexToCheck = rowIndex + direction.XOffset; + var colIndexToCheck = colIndex + direction.YOffset; + + if (IsOffGrid(rowIndexToCheck, colIndexToCheck)) + { + return false; + } + + var characterToCheck = input[rowIndexToCheck][colIndexToCheck]; + + if (characterToCheck != 'S' && characterToCheck != 'M') + { + return false; + } + + xmasMap.Add(direction, characterToCheck); + } + + return IsValidXmas(xmasMap); + } + + private bool IsValidXmas(Dictionary map) + { + var upToTheRight = map[Direction.UpToTheRight]; + var downToTheLeft = map[Direction.DownToTheLeft]; + var upToTheLeft = map[Direction.UpToTheLeft]; + var downToTheRight = map[Direction.DownToTheRight]; + + return upToTheRight != downToTheLeft && upToTheLeft != downToTheRight; + } + + private bool FoundWord(string word, Direction direction, int rowIndex, int columnIndex) + { + for (var currentTargetCharacterIndex = 0; currentTargetCharacterIndex < word.Length; currentTargetCharacterIndex++) + { + var currentTargetCharacter = word[currentTargetCharacterIndex]; + var currentCharacterRowIndex = rowIndex + (direction.XOffset * currentTargetCharacterIndex); + var currentCharacterColumnIndex = columnIndex + (direction.YOffset * currentTargetCharacterIndex); + + if (IsOffGrid(currentCharacterRowIndex, currentCharacterColumnIndex)) + { + return false; + } + + var currentCharacter = input[currentCharacterRowIndex][currentCharacterColumnIndex]; + + if (currentCharacter != currentTargetCharacter) + { + return false; + } + } + + return true; + } + + private bool IsOffGrid(int rowIndex, int columnIndex) + { + return rowIndex < 0 || + rowIndex > input.Length - 1 || + columnIndex < 0 || + columnIndex > input[0].Length - 1; + } +} \ No newline at end of file diff --git a/04/CeresSearch/Program.cs b/04/CeresSearch/Program.cs index 3751555..3df8bd7 100644 --- a/04/CeresSearch/Program.cs +++ b/04/CeresSearch/Program.cs @@ -1,2 +1,30 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using System.Diagnostics; + +using CeresSearch; + +if (args.Length is 0) +{ + Console.WriteLine("Please provide a path to the input file."); + return; +} + +if (File.Exists(args[0]) is false) +{ + Console.WriteLine("The provided file does not exist."); + return; +} + +var isPart2 = args.Length is 2 && args[1] is "part2"; +var input = await File.ReadAllLinesAsync(args[0]); + +var stopwatch = new Stopwatch(); +stopwatch.Start(); + +var grid = new Grid(input); + +var results = isPart2 + ? grid.CountXmases() + : grid.CountOccurrences("XMAS"); + +stopwatch.Stop(); +Console.WriteLine($"The number of occurrences is {results}. ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file