diff --git a/12/GardenGroups.Tests/RegionTests.cs b/12/GardenGroups.Tests/RegionTests.cs index fa279c0..63d0531 100644 --- a/12/GardenGroups.Tests/RegionTests.cs +++ b/12/GardenGroups.Tests/RegionTests.cs @@ -31,4 +31,15 @@ public class RegionTests await Assert.That(result).IsEqualTo(0); } + + [Test] + public async Task AddPosition_WhenCalled_ItShouldAddAPosition() + { + var region = Region.From('A'); + + region.AddPosition(new(0, 0, 'A')); + + await Assert.That(region.Positions).IsEquivalentTo(new HashSet() { new(0, 0, 'A') }); + await Assert.That(region.Area).IsEqualTo(1); + } } \ No newline at end of file diff --git a/12/GardenGroups/Direction.cs b/12/GardenGroups/Direction.cs new file mode 100644 index 0000000..52b7502 --- /dev/null +++ b/12/GardenGroups/Direction.cs @@ -0,0 +1,9 @@ +namespace GardenGroups; + +record Direction(int Xd, int Yd) +{ + 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); +} \ No newline at end of file diff --git a/12/GardenGroups/Garden.cs b/12/GardenGroups/Garden.cs new file mode 100644 index 0000000..8122a66 --- /dev/null +++ b/12/GardenGroups/Garden.cs @@ -0,0 +1,112 @@ +namespace GardenGroups; + +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 positionsToVisit = new Queue(); + + positionsToVisit.Enqueue(position); + + while (positionsToVisit.Count is not 0) + { + var currentPosition = positionsToVisit.Dequeue(); + + if (visitedPositions.Contains(currentPosition)) + { + continue; + } + + region.AddPosition(currentPosition); + visitedPositions.Add(currentPosition); + + 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); + } + } + + 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); + } + } + } +} \ No newline at end of file diff --git a/12/GardenGroups/Position.cs b/12/GardenGroups/Position.cs new file mode 100644 index 0000000..7e584be --- /dev/null +++ b/12/GardenGroups/Position.cs @@ -0,0 +1,29 @@ +namespace GardenGroups; + +record Position(int X, int Y, char PlantType) +{ + public Position GetNextPosition(Direction direction) => new(X + direction.Xd, Y + direction.Yd, '.'); + + public bool TryGetNextPosition(Direction direction, string[] grid, out Position nextPosition) + { + nextPosition = GetNextPosition(direction); + + var isOffGrid = IsOffGrid(nextPosition, grid); + + if (isOffGrid) + { + return false; + } + + nextPosition = nextPosition with { PlantType = grid[nextPosition.Y][nextPosition.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; + } +} \ No newline at end of file diff --git a/12/GardenGroups/Program.cs b/12/GardenGroups/Program.cs index 904d873..1aeddd3 100644 --- a/12/GardenGroups/Program.cs +++ b/12/GardenGroups/Program.cs @@ -1,5 +1,7 @@ using System.Diagnostics; +using GardenGroups; + if (args.Length is 0) { Console.WriteLine("Please provide a path to the input file."); @@ -18,183 +20,10 @@ var input = await File.ReadAllLinesAsync(args[0]); var stopwatch = new Stopwatch(); stopwatch.Start(); -var result = Garden.From(input).CalculateFencePriceBasedOnPerimeter(); +var garden = Garden.From(input); +var result = isPart2 + ? garden.CalculateFencePriceBasedOnSides() + : garden.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 +Console.WriteLine($"The total cost to fence the garden is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file diff --git a/12/GardenGroups/Region.cs b/12/GardenGroups/Region.cs new file mode 100644 index 0000000..2414d10 --- /dev/null +++ b/12/GardenGroups/Region.cs @@ -0,0 +1,99 @@ +namespace GardenGroups; + +class Region +{ + public char PlantType { get; } + public HashSet Positions { get; } = []; + public int Area => Positions.Count; + public int Perimeter { 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 AddPosition(Position position) => Positions.Add(position); + public void IncreasePerimeter() => Perimeter++; + public int CalculateFencePriceBasedOnPerimeter() => Area * Perimeter; + public int CalculateFencePriceBasedOnSides() + { + // corners is always + // equal to the number + // of sides + var corners = FindCorners(); + return corners * Area; + } + + private int FindCorners() + { + var numberOfCorners = 0; + + foreach (var position in Positions) + { + var hasNeighborAbove = HasNeighborInDirection(Direction.Up, position); + var hasNeighborRight = HasNeighborInDirection(Direction.Right, position); + var hasNeighborBelow = HasNeighborInDirection(Direction.Down, position); + var hasNeighborLeft = HasNeighborInDirection(Direction.Left, position); + + // X + // XRX + // X + if (hasNeighborRight is false && hasNeighborAbove is false) + { + numberOfCorners++; + } + + if (hasNeighborRight is false && hasNeighborBelow is false) + { + numberOfCorners++; + } + + if (hasNeighborLeft is false && hasNeighborAbove is false) + { + numberOfCorners++; + } + + if (hasNeighborLeft is false && hasNeighborBelow is false) + { + numberOfCorners++; + } + + // XRX + // RRR + // XRX + var nextPositionRight = position.GetNextPosition(Direction.Right); + var nextPositionLeft = position.GetNextPosition(Direction.Left); + + if (hasNeighborRight && hasNeighborAbove && HasNeighborInDirection(Direction.Up, nextPositionRight) is false) + { + numberOfCorners++; + } + + if (hasNeighborRight && hasNeighborBelow && HasNeighborInDirection(Direction.Down, nextPositionRight) is false) + { + numberOfCorners++; + } + + if (hasNeighborLeft && hasNeighborAbove && HasNeighborInDirection(Direction.Up, nextPositionLeft) is false) + { + numberOfCorners++; + } + + if (hasNeighborLeft && hasNeighborBelow && HasNeighborInDirection(Direction.Down, nextPositionLeft) is false) + { + numberOfCorners++; + } + } + + return numberOfCorners; + } + + private bool HasNeighborInDirection(Direction direction, Position position) + { + var nextPosition = position.GetNextPosition(direction); + return Positions.Any(p => p.X == nextPosition.X && p.Y == nextPosition.Y); + } +} \ No newline at end of file diff --git a/README.md b/README.md index c8270b5..fdd41b0 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,4 @@ dotnet build | 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/DiskFragmenter/) | Doing things with indexes is error prone. | | 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/HoofIt/) | I recognized the solution for this based on pattern from last year. Solved using BFS. | | 11 | [Problem](./11/PROBLEM.md) | [Solution](./11/PlutonianPebbles/) | Dictionary > List | +| 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/GardenGroups/) | CORNERS == SIDES |