From b10320ae094f3f3548fca2e355a228175123fc7c Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:03:40 -0600 Subject: [PATCH 1/2] feat: stalled on day 12 part 2 --- 12/GardenGroups.Tests/GardenTests.cs | 6 ++++++ 12/GardenGroups.Tests/RegionTests.cs | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 12/GardenGroups.Tests/GardenTests.cs create mode 100644 12/GardenGroups.Tests/RegionTests.cs diff --git a/12/GardenGroups.Tests/GardenTests.cs b/12/GardenGroups.Tests/GardenTests.cs new file mode 100644 index 0000000..13f03ec --- /dev/null +++ b/12/GardenGroups.Tests/GardenTests.cs @@ -0,0 +1,6 @@ +namespace GardenGroups.Tests; + +public class GardenTests +{ + +} \ No newline at end of file diff --git a/12/GardenGroups.Tests/RegionTests.cs b/12/GardenGroups.Tests/RegionTests.cs new file mode 100644 index 0000000..dadf4fb --- /dev/null +++ b/12/GardenGroups.Tests/RegionTests.cs @@ -0,0 +1,6 @@ +namespace GardenGroups.Tests; + +public class RegionTests +{ + +} \ No newline at end of file From 36806c530eea069ebb09f97ed901e0b3639c61ff Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:21:50 -0600 Subject: [PATCH 2/2] feat: stalled on day 12 part 2 --- 12/GardenGroups.Tests/GardenTests.cs | 94 +++++++++++++ 12/GardenGroups.Tests/RegionTests.cs | 28 ++++ 12/GardenGroups/Program.cs | 202 ++++++++++++++++++++++++++- 3 files changed, 322 insertions(+), 2 deletions(-) diff --git a/12/GardenGroups.Tests/GardenTests.cs b/12/GardenGroups.Tests/GardenTests.cs index 13f03ec..0958959 100644 --- a/12/GardenGroups.Tests/GardenTests.cs +++ b/12/GardenGroups.Tests/GardenTests.cs @@ -2,5 +2,99 @@ public class GardenTests { + [Test] + [MethodDataSource(nameof(CalculateFencePriceBasedOnPerimeterTestCases))] + public async Task CalculateFencePriceBasedOnPerimeter_WhenCalled_ItShouldReturnExpectedValue(CalculateFencePriceTestCase testCase) + { + var result = Garden.From(testCase.Input).CalculateFencePriceBasedOnPerimeter(); + + await Assert.That(result).IsEqualTo(testCase.ExpectedPrice); + } + [Test] + [MethodDataSource(nameof(CalculateFencePriceBasedOnSidesTestCases))] + public async Task CalculateFencePriceBasedOnSides_WhenCalled_ItShouldReturnExpectedValue(CalculateFencePriceTestCase testCase) + { + var result = Garden.From(testCase.Input).CalculateFencePriceBasedOnSides(); + + await Assert.That(result).IsEqualTo(testCase.ExpectedPrice); + } + + public static IEnumerable> CalculateFencePriceBasedOnSidesTestCases() + { + yield return () => new(["AAAA", "BBCD", "BBCC", "EEEC"], 80); + + yield return () => new( + [ + "EEEEE", + "EXXXX", + "EEEEE", + "EXXXX", + "EEEEE", + ], + 236 + ); + + yield return () => new( + [ + "AAAAAA", + "AAABBA", + "AAABBA", + "ABBAAA", + "ABBAAA", + "AAAAAA", + ], + 368 + ); + + yield return () => new( + [ + "RRRRIICCFF", + "RRRRIICCCF", + "VVRRRCCFFF", + "VVRCCCJFFF", + "VVVVCJJCFE", + "VVIVCCJJEE", + "VVIIICJJEE", + "MIIIIIJJEE", + "MIIISIJEEE", + "MMMISSJEEE", + ], + 1206 + ); + } + + public static IEnumerable> CalculateFencePriceBasedOnPerimeterTestCases() + { + yield return () => new(["AAAA", "BBCD", "BBCC", "EEEC"], 140); + + yield return () => new( + [ + "OOOOO", + "OXOXO", + "OOOOO", + "OXOXO", + "OOOOO", + ], + 772 + ); + + yield return () => new( + [ + "RRRRIICCFF", + "RRRRIICCCF", + "VVRRRCCFFF", + "VVRCCCJFFF", + "VVVVCJJCFE", + "VVIVCCJJEE", + "VVIIICJJEE", + "MIIIIIJJEE", + "MIIISIJEEE", + "MMMISSJEEE", + ], + 1930 + ); + } + + public record CalculateFencePriceTestCase(string[] Input, int ExpectedPrice); } \ No newline at end of file diff --git a/12/GardenGroups.Tests/RegionTests.cs b/12/GardenGroups.Tests/RegionTests.cs index dadf4fb..fa279c0 100644 --- a/12/GardenGroups.Tests/RegionTests.cs +++ b/12/GardenGroups.Tests/RegionTests.cs @@ -2,5 +2,33 @@ public class RegionTests { + [Test] + public async Task Region_WhenInstanceConstructed_ItShouldSetPlantTypeProperty() + { + var plantType = 'A'; + + var region = Region.From('A'); + + await Assert.That(region.PlantType).IsEqualTo(plantType); + } + + [Test] + public async Task IncreasePerimeter_WhenCalled_ItShouldIncreaseRegionPerimeter() + { + var region = Region.From('A'); + + region.IncreasePerimeter(); + + await Assert.That(region.Perimeter).IsEqualTo(1); + } + [Test] + public async Task CalculatePrice_WhenCalled_ItShouldReturnExpectedValue() + { + var region = Region.From('A'); + + var result = region.CalculateFencePriceBasedOnPerimeter(); + + await Assert.That(result).IsEqualTo(0); + } } \ No newline at end of file diff --git a/12/GardenGroups/Program.cs b/12/GardenGroups/Program.cs index 3751555..904d873 100644 --- a/12/GardenGroups/Program.cs +++ b/12/GardenGroups/Program.cs @@ -1,2 +1,200 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using System.Diagnostics; + +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 result = Garden.From(input).CalculateFencePriceBasedOnPerimeter(); + +stopwatch.Stop(); +Console.WriteLine($"The total cost to fence the garden is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); + +class Garden +{ + private static readonly Direction[] Directions = [ + Direction.Up, + Direction.Down, + Direction.Left, + Direction.Right, + ]; + + private readonly string[] _grid; + + private Garden(string[] grid) + { + _grid = grid; + } + + public static Garden From(string[] input) => new(input); + + public int CalculateFencePriceBasedOnSides() + { + var visitedPositions = new HashSet(); + var total = 0; + + WalkGarden((x, y) => + { + var currentPosition = new Position(x, y, _grid[y][x]); + + if (visitedPositions.Contains(currentPosition)) + { + return; + } + + var region = FindRegion(currentPosition, visitedPositions); + total += region.CalculateFencePriceBasedOnSides(); + }); + + return total; + } + + public int CalculateFencePriceBasedOnPerimeter() + { + var visitedPositions = new HashSet(); + var total = 0; + + WalkGarden((x, y) => + { + var currentPosition = new Position(x, y, _grid[y][x]); + + if (visitedPositions.Contains(currentPosition)) + { + return; + } + + var region = FindRegion(currentPosition, visitedPositions); + total += region.CalculateFencePriceBasedOnPerimeter(); + }); + + return total; + } + + private Region FindRegion(Position position, HashSet visitedPositions) + { + var region = Region.From(position); + var regionPositions = new HashSet(); + + var positionsToVisit = new Queue(); + + positionsToVisit.Enqueue(position); + + while (positionsToVisit.Count is not 0) + { + var currentPosition = positionsToVisit.Dequeue(); + + if (visitedPositions.Contains(currentPosition)) + { + continue; + } + + regionPositions.Add(currentPosition); + region.IncreaseArea(); + + foreach (var direction in Directions) + { + if ( + currentPosition.TryGetNextPosition(direction, _grid, out var newPosition) is false || + newPosition.PlantType != currentPosition.PlantType + ) + { + region.IncreasePerimeter(); + continue; + } + + positionsToVisit.Enqueue(newPosition); + } + } + + // TODO: Do something here to identify how many + // sides each position contributes in a region + + return region; + } + + private void WalkGarden(Action callback) + { + for (var y = 0; y < _grid.Length; y++) + { + var row = _grid[y]; + + for (var x = 0; x < row.Length; x++) + { + callback(x, y); + } + } + } +} + +class Region +{ + public char PlantType { get; } + public int Area { get; private set; } + public int Perimeter { get; private set; } + public int Sides { get; private set; } + + private Region(char plantType) + { + PlantType = plantType; + } + + public static Region From(char plantType) => new(plantType); + public static Region From(Position position) => new(position.PlantType); + + public void IncreaseArea() => Area++; + public void IncreasePerimeter() => Perimeter++; + public void IncreaseSize() => Sides++; + public int CalculateFencePriceBasedOnPerimeter() => Area * Perimeter; + + public int CalculateFencePriceBasedOnSides() => Area * Sides; +} + +record Position(int X, int Y, char PlantType) +{ + public bool TryGetNextPosition(Direction direction, string[] grid, out Position nextPosition) + { + var x = X + direction.XD; + var y = Y + direction.YD; + + nextPosition = new(x, y, '.'); + + var isOffGrid = IsOffGrid(nextPosition, grid); + + if (isOffGrid) + { + return false; + } + + nextPosition = nextPosition with { PlantType = grid[y][x] }; + return true; + } + + private static bool IsOffGrid(Position position, string[] grid) + { + return position.X < 0 || + position.X > grid[0].Length - 1 || + position.Y > grid.Length - 1 || + position.Y < 0; + } +} + +record Direction(int XD, int YD) +{ + public static Direction Up = new(0, -1); + public static Direction Down = new(0, 1); + public static Direction Left = new(-1, 0); + public static Direction Right = new(1, 0); +} \ No newline at end of file