Add AoC 2025 project structure and first solutions

This commit is contained in:
Sebastian Lindemeier 2025-12-04 21:40:42 +01:00
parent c83642d286
commit 1e98d90c33
15 changed files with 754 additions and 0 deletions

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,7 @@
namespace AdvenOfCode.Contracts;
public interface IPuzzleSolver<T>
{
public T SolvePart1(string puzzleInputPath);
public T SolvePart2(string puzzleInputPath);
}

41
AdventOfCode.sln Normal file
View File

@ -0,0 +1,41 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AoC_2025", "AoC_2025", "{05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AoC_2025", "AoC_2025\AoC_2025.csproj", "{4F395AE3-DABB-4546-A4D0-5A62425FE168}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AoC_2025.Tests", "AoC_2025.Tests\AoC_2025.Tests.csproj", "{197D9628-C2D1-4B33-9DB8-134F87F79904}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AoC_2025.Runner", "AoC_2025.Runner\AoC_2025.Runner.csproj", "{FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvenOfCode.Contracts", "AdvenOfCode.Contracts\AdvenOfCode.Contracts.csproj", "{0249A329-D5C1-4699-88BE-A668B362432E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4F395AE3-DABB-4546-A4D0-5A62425FE168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F395AE3-DABB-4546-A4D0-5A62425FE168}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F395AE3-DABB-4546-A4D0-5A62425FE168}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F395AE3-DABB-4546-A4D0-5A62425FE168}.Release|Any CPU.Build.0 = Release|Any CPU
{197D9628-C2D1-4B33-9DB8-134F87F79904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{197D9628-C2D1-4B33-9DB8-134F87F79904}.Debug|Any CPU.Build.0 = Debug|Any CPU
{197D9628-C2D1-4B33-9DB8-134F87F79904}.Release|Any CPU.ActiveCfg = Release|Any CPU
{197D9628-C2D1-4B33-9DB8-134F87F79904}.Release|Any CPU.Build.0 = Release|Any CPU
{FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FAB5E28F-706A-4DE1-B5FD-54E31C3A8405}.Release|Any CPU.Build.0 = Release|Any CPU
{0249A329-D5C1-4699-88BE-A668B362432E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0249A329-D5C1-4699-88BE-A668B362432E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0249A329-D5C1-4699-88BE-A668B362432E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0249A329-D5C1-4699-88BE-A668B362432E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4F395AE3-DABB-4546-A4D0-5A62425FE168} = {05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}
{197D9628-C2D1-4B33-9DB8-134F87F79904} = {05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}
{FAB5E28F-706A-4DE1-B5FD-54E31C3A8405} = {05EBAA1C-B33B-4AFE-8635-A58D7CA509A7}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AoC_2025\AoC_2025.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,28 @@
// See https://aka.ms/new-console-template for more information
using System.Globalization;
using AoC_2025;
var day01 = new Day01();
const string day01Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day01.txt";
Console.WriteLine(day01.SolvePart1(day01Path));
Console.WriteLine(day01.SolvePart2(day01Path));
Console.WriteLine();
var day02 = new Day02();
const string day02Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day02.txt";
Console.WriteLine(day02.SolvePart1(day02Path));
Console.WriteLine(day02.SolvePart2(day02Path));
Console.WriteLine();
var day03 = new Day03();
const string day03Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day03.txt";
Console.WriteLine(day03.SolvePart1(day03Path));
Console.WriteLine(day03.SolvePart2(day03Path));
Console.WriteLine();
var day04 = new Day04();
const string day04Path = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day04.txt";
Console.WriteLine(day04.SolvePart1(day04Path));
Console.WriteLine(day04.SolvePart2(day04Path));
Console.WriteLine();

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
<PackageReference Include="xunit" Version="2.9.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"/>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AoC_2025\AoC_2025.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,46 @@
using AdvenOfCode.Contracts;
namespace AoC_2025.Tests;
public class Day01Test
{
private readonly IPuzzleSolver<int> _sut;
private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day01.txt";
private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day01.txt";
public Day01Test()
{
_sut = new Day01();
}
[Fact]
public void Part01_Test_equals_3()
{
var actual = _sut.SolvePart1(TestInputPath);
Assert.Equal(3, actual);
}
[Fact]
public void Part01_Prod_equals_1097()
{
var actual = _sut.SolvePart1(ProdInputPath);
Assert.Equal(1097, actual);
}
[Fact]
public void Part02_Test_equals_16()
{
var actual = _sut.SolvePart2(TestInputPath);
Assert.Equal(16, actual);
}
[Fact]
public void Part02_Prod_equals_7101()
{
var actual = _sut.SolvePart2(ProdInputPath);
Assert.Equal(7101, actual);
}
}

View File

@ -0,0 +1,46 @@
using AdvenOfCode.Contracts;
namespace AoC_2025.Tests;
public class Day02Test
{
private readonly IPuzzleSolver<long> _sut;
private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day02.txt";
private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day02.txt";
public Day02Test()
{
_sut = new Day02();
}
[Fact]
public void Part01_Test_equals_1227775554()
{
var actual = _sut.SolvePart1(TestInputPath);
Assert.Equal(1227775554, actual);
}
[Fact]
public void Part01_Prod_equals_13919717792()
{
var actual = _sut.SolvePart1(ProdInputPath);
Assert.Equal(13919717792, actual);
}
[Fact]
public void Part02_Test_equals_4174379265()
{
var actual = _sut.SolvePart2(TestInputPath);
Assert.Equal(4174379265, actual);
}
[Fact]
public void Part02_Prod_equals_14582313461()
{
var actual = _sut.SolvePart2(ProdInputPath);
Assert.Equal(14582313461, actual);
}
}

View File

@ -0,0 +1,46 @@
using AdvenOfCode.Contracts;
namespace AoC_2025.Tests;
public class Day03Test
{
private readonly IPuzzleSolver<long> _sut;
private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day03.txt";
private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day03.txt";
public Day03Test()
{
_sut = new Day03();
}
[Fact]
public void Part01_Test_equals_357()
{
var actual = _sut.SolvePart1(TestInputPath);
Assert.Equal(357, actual);
}
[Fact]
public void Part01_Prod_equals_16812()
{
var actual = _sut.SolvePart1(ProdInputPath);
Assert.Equal(16812, actual);
}
[Fact]
public void Part02_Test_equals_3121910778619()
{
var actual = _sut.SolvePart2(TestInputPath);
Assert.Equal(3121910778619, actual);
}
[Fact]
public void Part02_Prod_equals_166345822896410()
{
var actual = _sut.SolvePart2(ProdInputPath);
Assert.Equal(166345822896410, actual);
}
}

View File

@ -0,0 +1,46 @@
using AdvenOfCode.Contracts;
namespace AoC_2025.Tests;
public class Day04Test
{
private readonly IPuzzleSolver<long> _sut;
private const string TestInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Test\day04.txt";
private const string ProdInputPath = @"C:\Users\p01004051\Documents\AoC-PuzzleInputs\2025\Prod\day04.txt";
public Day04Test()
{
_sut = new Day04();
}
[Fact]
public void Part01_Test_equals_13()
{
var actual = _sut.SolvePart1(TestInputPath);
Assert.Equal(13, actual);
}
[Fact]
public void Part01_Prod_equals_1411()
{
var actual = _sut.SolvePart1(ProdInputPath);
Assert.Equal(1411, actual);
}
[Fact]
public void Part02_Test_equals_43()
{
var actual = _sut.SolvePart2(TestInputPath);
Assert.Equal(43, actual);
}
[Fact]
public void Part02_Prod_equals_8557()
{
var actual = _sut.SolvePart2(ProdInputPath);
Assert.Equal(8557, actual);
}
}

13
AoC_2025/AoC_2025.csproj Normal file
View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AdvenOfCode.Contracts\AdvenOfCode.Contracts.csproj" />
</ItemGroup>
</Project>

112
AoC_2025/Day01.cs Normal file
View File

@ -0,0 +1,112 @@
using System;
using System.IO;
using System.Linq;
using AdvenOfCode.Contracts;
namespace AoC_2025;
public class Day01 : IPuzzleSolver<int>
{
private string[] ParsePuzzleInput(string path)
{
var puzzleInput = File.ReadAllLines(path)
.Where(l => !string.IsNullOrWhiteSpace(l))
.ToArray();
return puzzleInput;
}
public int SolvePart1(string pathToPuzzleInput)
{
var rotationInstructions = ParsePuzzleInput(pathToPuzzleInput);
var dialPosition = 50;
var timesZeroWasHit = 0;
foreach (var rotationAmount in rotationInstructions.Select(ParseRotationAmount))
{
dialPosition = (dialPosition + rotationAmount) % 100;
if (dialPosition == 0)
timesZeroWasHit++;
}
return timesZeroWasHit;
}
public int SolvePart2(string pathToPuzzleInput)
{
var rotationInstructions = ParsePuzzleInput(pathToPuzzleInput);
var dialPosition = 50;
var timesZeroWasHit = 0;
foreach (var rotationAmount in rotationInstructions.Select(ParseRotationAmount))
{
var actualRotationAmount = 0;
if (rotationAmount < 0)
{
(var fullTurns, actualRotationAmount) = DivMod(rotationAmount, -100);
timesZeroWasHit += fullTurns;
if (dialPosition != 0 && dialPosition + actualRotationAmount <= 0)
timesZeroWasHit++;
}
else
{
(var fullTurns, actualRotationAmount) = DivMod(rotationAmount, 100);
timesZeroWasHit += fullTurns;
if (dialPosition + actualRotationAmount >= 100)
timesZeroWasHit++;
}
dialPosition = Mod(dialPosition + actualRotationAmount, 100);
}
return timesZeroWasHit;
}
private (int div, int mod) DivMod(int number, int divisor)
{
return (number / divisor, Mod(number, divisor));
}
private int Mod(int number, int divident)
{
var mod = number % divident;
if (number < 0 && divident > 0)
{
mod = divident + mod;
}
return mod;
}
private static bool HasPassedZero(bool hasOverflow, int dialPosition)
{
return dialPosition != 0 && hasOverflow;
}
private static (int nextDialPosition, bool passedZero) GetNextDialPosition(int rotationAmount, int dialPosition)
{
var actualRotation = rotationAmount % 100;
var nextDialPosition = dialPosition + actualRotation;
var hasOverflow = nextDialPosition is > 99 or < 1;
nextDialPosition %= 100;
if (nextDialPosition < 0)
{
nextDialPosition = 100 + nextDialPosition;
}
return (nextDialPosition, hasOverflow);
}
private static int ParseRotationAmount(string puzzleInput)
{
var direction = puzzleInput[..1];
var rotationAmount = int.Parse(puzzleInput[1..]);
if (direction == "L")
{
rotationAmount *= -1;
}
return rotationAmount;
}
private static int CountFullRotations(int rotationAmount)
{
var fullRotations = Math.Abs(rotationAmount / 100);
return fullRotations;
}
}

131
AoC_2025/Day02.cs Normal file
View File

@ -0,0 +1,131 @@
using AdvenOfCode.Contracts;
namespace AoC_2025;
public class Day02 : IPuzzleSolver<long>
{
private string[] ParsePuzzleInput(string path)
{
var puzzleInput = File.ReadAllText(path)
.Trim()
.Split(',', StringSplitOptions.RemoveEmptyEntries);
return puzzleInput;
}
public long SolvePart1(string pathToPuzzleInput)
{
var idRanges = ParsePuzzleInput(pathToPuzzleInput);
var invalidIds = idRanges.SelectMany(range => FindInvalidIds(range));
var sum = invalidIds.Sum();
return sum;
}
public long SolvePart2(string pathToPuzzleInput)
{
var idRanges = ParsePuzzleInput(pathToPuzzleInput);
var invalidIds = idRanges.SelectMany(range => FindInvalidSegmentedIds(range));
var sum = invalidIds.Sum();
return sum;
}
private IEnumerable<long> FindInvalidIds(string range)
{
var (start, end) = Parse(range);
var halvableIds = FindHalvableIds(start, end);
var invalidIds = GetInvalidIds(halvableIds);
return invalidIds;
}
private IEnumerable<long> FindInvalidSegmentedIds(string range)
{
var (start, end) = Parse(range);
var idsAsAllSegments = GetIdAsAllSegments(start, end);
var invalidIdSegments = FindIdsWhereAllSegmentsAreEqual(idsAsAllSegments);
var invalidIds = MergeSegments(invalidIdSegments);
var invalidIdNumbers = invalidIds.Select(id => long.Parse(id));
return invalidIdNumbers;
}
private (long start, long end) Parse(string range)
{
var parts = range.Split('-', StringSplitOptions.RemoveEmptyEntries);
return (long.Parse(parts[0]), long.Parse(parts[1]));
}
private IEnumerable<string> FindHalvableIds(long start, long end)
{
var ids = Range(start, end);
var idStrings = ids.Select(id => id.ToString());
var halvableIds = idStrings.Where(id => (id.Length % 2) == 0);
return halvableIds;
}
private IEnumerable<long> GetInvalidIds(IEnumerable<string> halvableIds)
{
var idParts = halvableIds.Select(id => (id[..(id.Length / 2)], id[(id.Length / 2)..]));
var invalidIds = idParts.Where(parts=> parts.Item1 == parts.Item2);
var invalidNumberedIds = invalidIds.Select(id => long.Parse(id.Item1 + id.Item2));
return invalidNumberedIds;
}
private IEnumerable<long> Range(long start, long end)
{
for (var i = start; i <= end; i++)
{
yield return i;
}
}
private IEnumerable<IEnumerable<IEnumerable<string>>> GetIdAsAllSegments(long start, long end)
{
var ids = Range(start, end);
var idStrings = ids.Select(id => id.ToString());
var idSegments = idStrings.Select(id => GetSegments(id));
return idSegments;
}
private IEnumerable<IEnumerable<string>> GetSegments(string id)
{
for (int i = id.Length / 2; i > 0; i--)
{
if (id.Length % i == 0)
{
yield return id.Chunk(i).Select(chunks => new string(chunks));
}
}
}
private IEnumerable<IEnumerable<string>> FindIdsWhereAllSegmentsAreEqual(IEnumerable<IEnumerable<IEnumerable<string>>> idAllSegments)
{
var segmentsWithOneAllMatching = FilterForAnyAllSegmentsEqual(idAllSegments);
var idSegments = GetFirstForEachSegments(segmentsWithOneAllMatching);
return idSegments;
}
private static IEnumerable<IEnumerable<string>> GetFirstForEachSegments(IEnumerable<IEnumerable<IEnumerable<string>>> segmentsWithOneAllMatching)
{
var idSegments = segmentsWithOneAllMatching
.Select(segments => segments.First());
return idSegments;
}
private IEnumerable<string> MergeSegments(IEnumerable<IEnumerable<string>> segments)
{
return segments.Select(segment => string.Concat(segment));
}
private IEnumerable<IEnumerable<IEnumerable<string>>> FilterForAnyAllSegmentsEqual(IEnumerable<IEnumerable<IEnumerable<string>>> idAllSegments)
{
var segmentsWithOneAllMatching = idAllSegments
.Where(idSegments => idSegments.Any(segments => CheckAllSegmentsEqual(segments)));
return segmentsWithOneAllMatching;
}
private bool CheckAllSegmentsEqual(IEnumerable<string> segments)
{
var (allEqual, _) = segments.Aggregate(
(allEqual: true, prev: ""),
(tuple, segment) => (tuple.allEqual && (tuple.prev == "" || tuple.prev == segment), segment));
return allEqual;
}
}

88
AoC_2025/Day03.cs Normal file
View File

@ -0,0 +1,88 @@
using System;
using System.IO;
using System.Linq;
using AdvenOfCode.Contracts;
namespace AoC_2025;
public class Day03 : IPuzzleSolver<long>
{
private string[] ParsePuzzleInput(string path)
{
var puzzleInput = File.ReadAllLines(path)
.Where(line => !string.IsNullOrWhiteSpace(line))
.ToArray();
return puzzleInput;
}
public long SolvePart1(string pathToPuzzleInput)
{
var batterieBankStrings = ParsePuzzleInput(pathToPuzzleInput);
var batterieBanks = batterieBankStrings.Select(bbs => ParseBatterieBank(bbs));
var voltages = batterieBanks.Select(bb => FindMaxVoltage(bb));
var sumVoltages = voltages.Sum();
return sumVoltages;
}
public long SolvePart2(string pathToPuzzleInput)
{
const int voltageLength = 12;
var batterieBankStrings = ParsePuzzleInput(pathToPuzzleInput);
var batterieBanks = batterieBankStrings.Select(bbs => ParseBatterieBank(bbs));
var voltages = batterieBanks.Select(bb => GetMaxSuperVoltage(bb, voltageLength));
var sumVoltages = voltages.Sum();
return sumVoltages;
}
private int[] ParseBatterieBank(string batterieBank)
{
return batterieBank
.Select(c => new string(c, 1))
.Select(value => int.Parse(value))
.ToArray();
}
private long FindMaxVoltage(Span<int> batterieBank)
{
var (firstNumber, position) = GetMax(batterieBank[..^1]);
var (secondNumber, _) = GetMax(batterieBank[(position + 1)..]);
var voltage = Combine(firstNumber, secondNumber);
return voltage;
}
private long Combine(long first, long second)
{
return (first * 10) + second;
}
private long GetMaxSuperVoltage(Span<int> batterieBank, int voltageLength)
{
var indexedVoltageLength = voltageLength - 1;
var currentBankPosition = 0;
var superVoltage = 0L;
for (var i = 0; i < voltageLength; i++)
{
var (max, numberPosition) = GetMax(batterieBank[currentBankPosition..^(indexedVoltageLength - i)]);
superVoltage = Combine(superVoltage, max);
currentBankPosition += numberPosition + 1;
}
return superVoltage;
}
private (int max, int currentPosition) GetMax(Span<int> batterieBank)
{
var max = batterieBank[0];
var currentPosition = 0;
for (var i = 1; i < batterieBank.Length; i++)
{
if (max < batterieBank[i])
{
max = batterieBank[i];
currentPosition = i;
}
}
return (max, currentPosition);
}
}

102
AoC_2025/Day04.cs Normal file
View File

@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AdvenOfCode.Contracts;
using Coordinate = (int x, int y);
namespace AoC_2025;
public class Day04 : IPuzzleSolver<long>
{
private string[] ParsePuzzleInput(string path)
{
var puzzleInput = File.ReadAllLines(path)
.Where(line => !string.IsNullOrWhiteSpace(line))
.Select(line => line.Trim())
.ToArray();
return puzzleInput;
}
public long SolvePart1(string pathToPuzzleInput)
{
var paperrollGrid = ParsePuzzleInput(pathToPuzzleInput);
var accessiblePaperrolls = GetAccessiblePaperrolls(paperrollGrid);
var countAccessibleRolls = accessiblePaperrolls.Count();
return countAccessibleRolls;
}
public long SolvePart2(string pathToPuzzleInput)
{
var paperrollGrid = ParsePuzzleInput(pathToPuzzleInput);
var rollCoordinates = GetRollCoordinates(paperrollGrid);
var countAccessibleRolls = RecursiveCountAllRemovableRolls(rollCoordinates);
return countAccessibleRolls;
}
private IEnumerable<Coordinate> GetAccessiblePaperrolls(IEnumerable<string> paperrollGrid)
{
var rollCoordinates = GetRollCoordinates(paperrollGrid);
var rollsWithNeighbours = GetNeighbours(rollCoordinates);
var accessiblePaperrolls = FilterForLessThan(rollsWithNeighbours, 4);
return accessiblePaperrolls;
}
private int RecursiveCountAllRemovableRolls(HashSet<Coordinate> coordinates)
{
var rollsWithNeighbours = GetNeighbours(coordinates);
var accessiblePaperrolls = FilterForLessThan(rollsWithNeighbours, 4).ToArray();
var countAccessibleRolls = accessiblePaperrolls.Length;
coordinates.ExceptWith(accessiblePaperrolls);
return countAccessibleRolls == 0
? 0
: countAccessibleRolls + RecursiveCountAllRemovableRolls(coordinates);
}
private HashSet<Coordinate> GetRollCoordinates(IEnumerable<string> paperrollGrid)
{
var coordinates = paperrollGrid
.SelectMany((gridLine, xIndex) => GetCoordinates(gridLine, xIndex))
.ToHashSet();
return coordinates;
}
private IEnumerable<Coordinate> GetCoordinates(string gridLine, int xIndex)
{
var indexedCharacters = gridLine.Select((character, yIndex) => (character, yIndex));
var validRolls = indexedCharacters.Where(tuple => tuple.character == '@');
var rollCoordinates = validRolls.Select(tuple => (xIndex, tuple.yIndex));
return rollCoordinates;
}
private IEnumerable<(Coordinate coordinate, Coordinate[] neighbours)> GetNeighbours(HashSet<Coordinate> rollCoordinates)
{
var coordsWithNeighbourCount = rollCoordinates
.Select(coord => (coord, GetNeighbours(coord, rollCoordinates)));
return coordsWithNeighbourCount;
}
private Coordinate[] GetNeighbours(Coordinate coordinate, HashSet<Coordinate> coordinates)
{
Coordinate[] neighbourDirections =
[
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1),
];
var possibleNeighbours = neighbourDirections
.Select(direction => (coordinate.x + direction.x, coordinate.y + direction.y));
var actualNeighbours = possibleNeighbours
.Where(coord => coordinates.Contains(coord))
.ToArray();
return actualNeighbours;
}
private IEnumerable<Coordinate> FilterForLessThan(IEnumerable<(Coordinate coordinate, Coordinate[] neighbours)> coordinatesWithNeighbours,
int lessThan)
{
var coordinatesWithLess = coordinatesWithNeighbours
.Where(entry => entry.neighbours.Count() < lessThan)
.Select(entry => entry.coordinate);
return coordinatesWithLess;
}
}