From 00bfb7d46e60ae9ff21eea5074f52579d120f18b Mon Sep 17 00:00:00 2001 From: AliveDevil Date: Wed, 25 Dec 2024 02:03:42 +0100 Subject: [PATCH] Finish Day 08 --- .vscode/launch.json | 11 +++ .vscode/tasks.json | 8 ++ AdventOfCode.sln | 15 +++- src/2024/08/Program.cs | 58 ++++++++++++ src/2024/08/ScenarioMap.cs | 31 +++++++ src/2024/08/aoc-2024-08.csproj | 15 ++++ src/{2024 => }/Directory.Build.props | 0 src/core/FileExistsAttribute.cs | 8 ++ src/core/SymbolGrid.cs | 126 +++++++++++++++++++++++++++ src/core/Vector2I.cs | 28 ++++++ src/core/core.csproj | 9 ++ 11 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 src/2024/08/Program.cs create mode 100644 src/2024/08/ScenarioMap.cs create mode 100644 src/2024/08/aoc-2024-08.csproj rename src/{2024 => }/Directory.Build.props (100%) create mode 100644 src/core/FileExistsAttribute.cs create mode 100644 src/core/SymbolGrid.cs create mode 100644 src/core/Vector2I.cs create mode 100644 src/core/core.csproj diff --git a/.vscode/launch.json b/.vscode/launch.json index 3cea784..c85dbeb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -80,6 +80,17 @@ "cwd": "${workspaceFolder}", "stopAtEntry": false, "console": "internalConsole" + }, + { + "name": "aoc-2024-08 Debug", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "dotnet: build aoc-2024-08", + "program": "${workspaceFolder}/artifacts/bin/aoc-2024-08/debug/aoc-2024-08.dll", + "args": ["part-one", "data/2024/08/dev.txt"], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a2c7f14..aa0cdd5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -58,6 +58,14 @@ "group": "build", "problemMatcher": [], "label": "dotnet: build aoc-2024-07" + }, + { + "type": "dotnet", + "task": "build aoc-2024-08.csproj", + "file": "src/2024/08/aoc-2024-08.csproj", + "group": "build", + "problemMatcher": [], + "label": "dotnet: build aoc-2024-08" } ] } \ No newline at end of file diff --git a/AdventOfCode.sln b/AdventOfCode.sln index 3694faa..129e7bd 100644 --- a/AdventOfCode.sln +++ b/AdventOfCode.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -19,6 +19,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoc-2024-06", "src\2024\06\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoc-2024-07", "src\2024\07\aoc-2024-07.csproj", "{58941BB2-E4AC-40F0-AA92-BEF51DEFC859}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoc-2024-08", "src\2024\08\aoc-2024-08.csproj", "{44BBC1CF-D2FC-4906-9691-B439EC9EFB8C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "core", "src\core\core.csproj", "{9E2BD5D6-55DD-4760-AD30-75B168F03F60}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,6 +60,14 @@ Global {58941BB2-E4AC-40F0-AA92-BEF51DEFC859}.Debug|Any CPU.Build.0 = Debug|Any CPU {58941BB2-E4AC-40F0-AA92-BEF51DEFC859}.Release|Any CPU.ActiveCfg = Release|Any CPU {58941BB2-E4AC-40F0-AA92-BEF51DEFC859}.Release|Any CPU.Build.0 = Release|Any CPU + {44BBC1CF-D2FC-4906-9691-B439EC9EFB8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44BBC1CF-D2FC-4906-9691-B439EC9EFB8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44BBC1CF-D2FC-4906-9691-B439EC9EFB8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44BBC1CF-D2FC-4906-9691-B439EC9EFB8C}.Release|Any CPU.Build.0 = Release|Any CPU + {9E2BD5D6-55DD-4760-AD30-75B168F03F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E2BD5D6-55DD-4760-AD30-75B168F03F60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E2BD5D6-55DD-4760-AD30-75B168F03F60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E2BD5D6-55DD-4760-AD30-75B168F03F60}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {FAF70800-E2C3-4AD7-B433-86C1F20380F7} = {37CA8017-085A-4F6A-BADB-535F929C10B9} @@ -65,5 +77,6 @@ Global {D108A6AB-B974-4BD9-B97A-CB1D1A2F3920} = {37CA8017-085A-4F6A-BADB-535F929C10B9} {EEC6EF36-EE16-4DB4-9AE8-CF0234751458} = {37CA8017-085A-4F6A-BADB-535F929C10B9} {58941BB2-E4AC-40F0-AA92-BEF51DEFC859} = {37CA8017-085A-4F6A-BADB-535F929C10B9} + {44BBC1CF-D2FC-4906-9691-B439EC9EFB8C} = {37CA8017-085A-4F6A-BADB-535F929C10B9} EndGlobalSection EndGlobal diff --git a/src/2024/08/Program.cs b/src/2024/08/Program.cs new file mode 100644 index 0000000..9e6eba5 --- /dev/null +++ b/src/2024/08/Program.cs @@ -0,0 +1,58 @@ +using System.Collections.Immutable; + +using ConsoleAppFramework; + +using core; + +var builder = ConsoleApp.Create(); +builder.Add("part-one", static ([Argument, FileExists] string file) => +{ + Console.WriteLine($"Result: {ReadFile(file, false)}"); +}); +builder.Add("part-two", static ([Argument, FileExists] string file) => +{ + Console.WriteLine($"Result: {ReadFile(file, true)}"); +}); +builder.Run(args); + +static int ReadFile(string path, bool resonant) +{ + SymbolGrid grid = new(); + grid.Load(path); + ScenarioMap map = new(grid); + HashSet antinodes = []; + foreach (var (key, set) in map.Antennas) + { + for (int l = set.Length - 1; l > 0; l--) + { + for (int r = 0; r < l; r++) + { + var d = set[l] - set[r]; + for (int i = resonant ? 1 : 2; resonant || i < 3; i++) + { + bool valid = false; + var fl = set[l] - d * i; + if (grid.IsValid(fl)) + { + valid = true; + antinodes.Add(fl); + } + + var fr = set[r] + d * i; + if (grid.IsValid(fr)) + { + valid = true; + antinodes.Add(fr); + } + + if (!valid) + { + break; + } + } + } + } + } + + return antinodes.Count; +} diff --git a/src/2024/08/ScenarioMap.cs b/src/2024/08/ScenarioMap.cs new file mode 100644 index 0000000..d4facc7 --- /dev/null +++ b/src/2024/08/ScenarioMap.cs @@ -0,0 +1,31 @@ +using System.Collections.Frozen; +using System.Collections.Immutable; +using System.Runtime.InteropServices; + +using core; + +record class ScenarioMap +{ + private readonly SymbolGrid _grid; + + public FrozenDictionary> Antennas { get; } + + public ScenarioMap(SymbolGrid grid) + { + _grid = grid; + Dictionary> antennas = []; + foreach (var (position, symbol) in grid) + { + if (char.IsLetterOrDigit(symbol)) + { + (CollectionsMarshal.GetValueRefOrAddDefault( + antennas, + symbol, + out _ + ) ??= []).Add(position); + } + } + + Antennas = antennas.ToFrozenDictionary(c => c.Key, c => (ImmutableArray)[.. c.Value]); + } +} diff --git a/src/2024/08/aoc-2024-08.csproj b/src/2024/08/aoc-2024-08.csproj new file mode 100644 index 0000000..c3937a2 --- /dev/null +++ b/src/2024/08/aoc-2024-08.csproj @@ -0,0 +1,15 @@ + + + + + + + + Exe + net9.0 + aoc_2024_08 + enable + enable + + + diff --git a/src/2024/Directory.Build.props b/src/Directory.Build.props similarity index 100% rename from src/2024/Directory.Build.props rename to src/Directory.Build.props diff --git a/src/core/FileExistsAttribute.cs b/src/core/FileExistsAttribute.cs new file mode 100644 index 0000000..53c1e69 --- /dev/null +++ b/src/core/FileExistsAttribute.cs @@ -0,0 +1,8 @@ +using System.ComponentModel.DataAnnotations; + +namespace core; + +public class FileExistsAttribute : ValidationAttribute +{ + public override bool IsValid(object? value) => value is string path && File.Exists(path); +} diff --git a/src/core/SymbolGrid.cs b/src/core/SymbolGrid.cs new file mode 100644 index 0000000..93b0cc1 --- /dev/null +++ b/src/core/SymbolGrid.cs @@ -0,0 +1,126 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.MemoryMappedFiles; +using System.Numerics; + +namespace core; + +public class SymbolGrid +{ + private char[]? _map; + + [DisallowNull] + public char? this[int x, int y] + { + get + { + EnsureMap(); + return IsValid(x, y) ? _map[y * Height + x] : null; + } + set + { + EnsureMap(); + if (IsValid(x, y)) + { + _map[y * Height + x] = value.Value; + } + } + } + + [DisallowNull] + public char? this[Vector2I i] + { + get => this[i.X, i.Y]; + set => this[i.X, i.Y] = value; + } + + public int Width { get; private set; } + + public int Height { get; private set; } + + public void Load(string file) + { + using var fileView = MemoryMappedFile.CreateFromFile(file, FileMode.Open); + using var accessor = fileView.CreateViewAccessor(); + (Width, var stride, Height) = Parse(accessor); + _map = new char[Width * Height]; + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + _map[y * Height + x] = (char)accessor.ReadByte(y * stride + x); + } + } + + static (int Width, int Stride, int Height) Parse(MemoryMappedViewAccessor map) + { + bool hasNewLine = false; + int? width = null, stride = null; + for (int i = 0; i < map.Capacity; i++) + { + if ((char)map.ReadByte(i) is '\r' or '\n') + { + if (!hasNewLine) + { + width = i; + } + + hasNewLine = true; + } + else if (hasNewLine) + { + stride = i; + break; + } + } + + return (width!.Value, stride!.Value, (int)((map.Capacity + width.Value) / stride.Value)); + } + } + + public SymbolEnumerator GetEnumerator() => new(this); + + public bool IsValid(Vector2I p) => IsValid(p.X, p.Y); + + public bool IsValid(int x, int y) + { + EnsureMap(); + return x >= 0 && x < Width && y >= 0 && y < Height; + } + + public ref struct SymbolEnumerator + { + private readonly SymbolGrid _grid; + private readonly int _length; + private int? _current; + + internal SymbolEnumerator(SymbolGrid grid) + { + _grid = grid; + _length = grid.Width * grid.Height; + } + + public (Vector2I Position, char Symbol) Current { get; private set; } + + public bool MoveNext() + { + _current = _current.HasValue ? _current + 1 : 0; + if (_current == _length) + { + return false; + } + + Current = (_grid.ToVector(_current.Value), _grid._map![_current.Value]); + + return true; + } + } + + private Vector2I ToVector(int index) => new() + { + Y = index / Height, + X = index % Height + }; + + [MemberNotNull(nameof(_map))] + private void EnsureMap() => ArgumentNullException.ThrowIfNull(_map); +} diff --git a/src/core/Vector2I.cs b/src/core/Vector2I.cs new file mode 100644 index 0000000..970c573 --- /dev/null +++ b/src/core/Vector2I.cs @@ -0,0 +1,28 @@ +using System.Numerics; + +namespace core; + +public record struct Vector2I(int X, int Y) : +IAdditionOperators, +ISubtractionOperators, +IMultiplyOperators +{ + public readonly double Length => Math.ReciprocalSqrtEstimate(SquareLength); + + public readonly int SquareLength => X * X + Y * Y; + + public static Vector2I operator +(Vector2I left, Vector2I right) + { + return new(left.X + right.X, left.Y + right.Y); + } + + public static Vector2I operator -(Vector2I left, Vector2I right) + { + return new(left.X - right.X, left.Y - right.Y); + } + + public static Vector2I operator *(Vector2I left, int right) + { + return new(left.X * right, left.Y * right); + } +} diff --git a/src/core/core.csproj b/src/core/core.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/src/core/core.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + +