From a043c574f5e5a71ea02c40c27c2d67d7f4f659d8 Mon Sep 17 00:00:00 2001 From: Sebastian Lindemeier Date: Tue, 9 Dec 2025 10:39:26 +0100 Subject: [PATCH] Add Day09 implementation with puzzle-solving logic and tests --- AoC_2025.Tests/Day09Test.cs | 47 ++++++++++++++++ AoC_2025/Day09.cs | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 AoC_2025.Tests/Day09Test.cs create mode 100644 AoC_2025/Day09.cs diff --git a/AoC_2025.Tests/Day09Test.cs b/AoC_2025.Tests/Day09Test.cs new file mode 100644 index 0000000..bba1084 --- /dev/null +++ b/AoC_2025.Tests/Day09Test.cs @@ -0,0 +1,47 @@ +using AdvenOfCode.Contracts; + +namespace AoC_2025.Tests; + +public class Day09Test +{ + private IPuzzleSolver _sut; + private static readonly string rootPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + private readonly string TestInputPath = @$"{rootPath}\AoC-PuzzleInputs\2025\Test\day09.txt"; + private readonly string ProdInputPath = @$"{rootPath}\AoC-PuzzleInputs\2025\Prod\day09.txt"; + + public Day09Test() + { + _sut = new Day09(); + } + [Fact] + public void Part01_Test_equals_50() + { + var actual = _sut.SolvePart1(TestInputPath); + + Assert.Equal(50, actual); + } + + [Fact] + public void Part01_Prod_equals_4754955192() + { + var actual = _sut.SolvePart1(ProdInputPath); + + Assert.Equal(4754955192, actual); + } + + [Fact] + public void Part02_Test_equals_24() + { + var actual = _sut.SolvePart2(TestInputPath); + + Assert.Equal(24, actual); + } + + [Fact] + public void Part02_Prod_equals_1568849600() + { + var actual = _sut.SolvePart2(ProdInputPath); + + Assert.Equal(1568849600, actual); + } +} \ No newline at end of file diff --git a/AoC_2025/Day09.cs b/AoC_2025/Day09.cs new file mode 100644 index 0000000..63cc277 --- /dev/null +++ b/AoC_2025/Day09.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.IO; +using System.Linq; +using AdvenOfCode.Contracts; +using Coordinate = (long x, long y); +using MinMax = (long minX, long minY, long maxX, long maxY); + +namespace AoC_2025; + +public class Day09 : IPuzzleSolver +{ + private Coordinate[] ParsePuzzleInput(string path) + { + var puzzleInput = File.ReadAllLines(path) + .Where(line => !string.IsNullOrWhiteSpace(line)) + .Select(line => line.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(str => long.Parse(str)).ToArray()) + .Select(numbers => (numbers[0], numbers[1])) + .ToArray(); + return puzzleInput; + } + + public long SolvePart1(string pathToPuzzleInput) + { + var redTiles = ParsePuzzleInput(pathToPuzzleInput); + var possibleRectangles = GetAllPossibleRectanglesAsMinMax(redTiles); + var areas = GetOrderedAreas(possibleRectangles); + return areas.First(); + } + + public long SolvePart2(string pathToPuzzleInput) + { + var redTiles = ParsePuzzleInput(pathToPuzzleInput); + var borders = GetBordersAsMinMax(redTiles); + var possibleRectangles = GetAllPossibleRectanglesAsMinMax(redTiles); + var containedRectangles = GetRectanglesContainedInBorders(possibleRectangles, borders); + var areas = GetOrderedAreas(containedRectangles); + return areas.First(); + } + + private IOrderedEnumerable GetOrderedAreas(IEnumerable<(long minX, long minY, long maxX, long maxY)> containedRectangles) + { + return containedRectangles + .Select(rectangle => GetArea((rectangle.maxX, rectangle.maxY), (rectangle.minX, rectangle.minY))) + .OrderDescending(); + } + + private IEnumerable<(long minX, long minY, long maxX, long maxY)> GetRectanglesContainedInBorders(IEnumerable<(long minX, long minY, long maxX, long maxY)> possibleRectangles, (long minX, long minY, long maxX, long maxY)[] borders) + { + return possibleRectangles + .Where(rectangle => IsRectangleInsideBorders(borders, rectangle)); + } + + private IEnumerable<(long minX, long minY, long maxX, long maxY)> GetAllPossibleRectanglesAsMinMax(Coordinate[] redTiles) + { + return Combinations(redTiles).Select(x => AsMinMax(x.a, x.b)); + } + + private MinMax[] GetBordersAsMinMax(Coordinate[] redTiles) + { + return GetBorder(redTiles).Select(x => AsMinMax(x.a, x.b)).ToArray(); + } + + private bool IsRectangleInsideBorders((long minX, long minY, long maxX, long maxY)[] borders, (long minX, long minY, long maxX, long maxY) rectangle) + { + return !borders.Any(border => RectangleOverlapsBorder(rectangle, border)); + } + + private MinMax AsMinMax(Coordinate a, Coordinate b) + { + return (Math.Min(a.x, b.x), Math.Min(a.y, b.y), Math.Max(a.x, b.x), Math.Max(a.y, b.y)); + } + + private bool RectangleOverlapsBorder(MinMax rectangle, MinMax borderSegment) + { + return rectangle.minX < borderSegment.maxX && rectangle.minY < borderSegment.maxY && + rectangle.maxX > borderSegment.minX && rectangle.maxY > borderSegment.minY; + } + + private long GetArea(Coordinate tileA, Coordinate tileB) + { + return (Math.Abs(tileA.x - tileB.x) + 1) * (Math.Abs(tileA.y - tileB.y) + 1); + } + + private IEnumerable<(Coordinate tileA, Coordinate tileB)> GetAllCombinationsSortedByArea(IEnumerable tiles) + { + var combinations = Combinations(tiles) + .OrderByDescending(x => GetArea(x.a, x.b)); + return combinations; + } + + private IEnumerable<(TValue a, TValue b)> Combinations(IEnumerable values) + { + var enumeratedValues = values.Index().ToArray(); + var pairs = + from a in enumeratedValues + from b in enumeratedValues + where a.Index < b.Index + select (a.Item, b.Item); + return pairs; + } + + private IEnumerable<(Coordinate a, Coordinate b)> GetBorder(IEnumerable tiles) + { + var allTiles = tiles.ToArray(); + return allTiles.Zip([..allTiles[1..], allTiles[0]], (a, b) => (a, b)); + } +} \ No newline at end of file