From 8b64d7b16f84c78d078ae912ab6340053de4000a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Moru=C5=9B?= Date: Tue, 2 Apr 2024 11:53:21 +0200 Subject: [PATCH] Added day 9 solution (#7) * Added day 9 scaffolding * Added part 1 README * Solved part 1 * Solver part 2 * Normalized line-ending * Remove my inputs from repo * Remove puzzle text from README * Move day 9 to solved solution folder * Added Rider run files --- .run/Day09 example.run.xml | 21 +++++ .run/Day09.run.xml | 20 ++++ AdventOfCode2023.sln | 7 ++ .../.vscode/launch.json | 18 ++++ Day09 - Mirage Maintenance/.vscode/tasks.json | 21 +++++ Day09 - Mirage Maintenance/Day09.csproj | 22 +++++ Day09 - Mirage Maintenance/Day09Solver.cs | 52 +++++++++++ .../Day09SolverOptions.cs | 7 ++ Day09 - Mirage Maintenance/Program.cs | 45 +++++++++ .../Puzzle/Oasis/Report.cs | 8 ++ .../Puzzle/Oasis/ReportReader.cs | 30 ++++++ .../Puzzle/Oasis/ValueHistory.cs | 91 +++++++++++++++++++ Day09 - Mirage Maintenance/README.md | 5 + Tests/Day09Tests.cs | 25 +++++ Tests/Inputs/Day09/example-input.txt | 3 + Tests/Tests.csproj | 1 + 16 files changed, 376 insertions(+) create mode 100644 .run/Day09 example.run.xml create mode 100644 .run/Day09.run.xml create mode 100644 Day09 - Mirage Maintenance/.vscode/launch.json create mode 100644 Day09 - Mirage Maintenance/.vscode/tasks.json create mode 100644 Day09 - Mirage Maintenance/Day09.csproj create mode 100644 Day09 - Mirage Maintenance/Day09Solver.cs create mode 100644 Day09 - Mirage Maintenance/Day09SolverOptions.cs create mode 100644 Day09 - Mirage Maintenance/Program.cs create mode 100644 Day09 - Mirage Maintenance/Puzzle/Oasis/Report.cs create mode 100644 Day09 - Mirage Maintenance/Puzzle/Oasis/ReportReader.cs create mode 100644 Day09 - Mirage Maintenance/Puzzle/Oasis/ValueHistory.cs create mode 100644 Day09 - Mirage Maintenance/README.md create mode 100644 Tests/Day09Tests.cs create mode 100644 Tests/Inputs/Day09/example-input.txt diff --git a/.run/Day09 example.run.xml b/.run/Day09 example.run.xml new file mode 100644 index 0000000..ee07b7b --- /dev/null +++ b/.run/Day09 example.run.xml @@ -0,0 +1,21 @@ + + + + diff --git a/.run/Day09.run.xml b/.run/Day09.run.xml new file mode 100644 index 0000000..0fc399e --- /dev/null +++ b/.run/Day09.run.xml @@ -0,0 +1,20 @@ + + + + diff --git a/AdventOfCode2023.sln b/AdventOfCode2023.sln index e98ab65..28f5e11 100644 --- a/AdventOfCode2023.sln +++ b/AdventOfCode2023.sln @@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Day07", "Day07 - Camel Card EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Day08", "Day08 - Haunted Wasteland\Day08.csproj", "{78787B01-0E1E-472E-8B58-4FFD864FC1F6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Day09", "Day09 - Mirage Maintenance\Day09.csproj", "{1E0917F9-35B3-4474-B335-E72C6BDFE667}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,6 +81,10 @@ Global {78787B01-0E1E-472E-8B58-4FFD864FC1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {78787B01-0E1E-472E-8B58-4FFD864FC1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {78787B01-0E1E-472E-8B58-4FFD864FC1F6}.Release|Any CPU.Build.0 = Release|Any CPU + {1E0917F9-35B3-4474-B335-E72C6BDFE667}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E0917F9-35B3-4474-B335-E72C6BDFE667}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E0917F9-35B3-4474-B335-E72C6BDFE667}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E0917F9-35B3-4474-B335-E72C6BDFE667}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {CE8086B8-A3E4-40E0-859F-607F95D25514} = {F6C05A63-6269-425F-B877-9E9F0BC1FC26} @@ -89,5 +95,6 @@ Global {6BA7E3DC-B970-457B-84FA-50034501273B} = {F6C05A63-6269-425F-B877-9E9F0BC1FC26} {0A495922-31C5-4E5D-84AB-95262108E1D9} = {F6C05A63-6269-425F-B877-9E9F0BC1FC26} {78787B01-0E1E-472E-8B58-4FFD864FC1F6} = {F6C05A63-6269-425F-B877-9E9F0BC1FC26} + {1E0917F9-35B3-4474-B335-E72C6BDFE667} = {F6C05A63-6269-425F-B877-9E9F0BC1FC26} EndGlobalSection EndGlobal diff --git a/Day09 - Mirage Maintenance/.vscode/launch.json b/Day09 - Mirage Maintenance/.vscode/launch.json new file mode 100644 index 0000000..d351db0 --- /dev/null +++ b/Day09 - Mirage Maintenance/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch (my input)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug/net8.0/Day09.dll", + "args": [ + "input.txt" + ], + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "stopAtEntry": false + } + ] +} diff --git a/Day09 - Mirage Maintenance/.vscode/tasks.json b/Day09 - Mirage Maintenance/.vscode/tasks.json new file mode 100644 index 0000000..21d9578 --- /dev/null +++ b/Day09 - Mirage Maintenance/.vscode/tasks.json @@ -0,0 +1,21 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "group": { + "kind": "build", + "isDefault": true + }, + "args": [ + "build", + "${workspaceFolder}/Day09.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/Day09 - Mirage Maintenance/Day09.csproj b/Day09 - Mirage Maintenance/Day09.csproj new file mode 100644 index 0000000..1bc52db --- /dev/null +++ b/Day09 - Mirage Maintenance/Day09.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + AdventOfCode.Year2023.Day09 + + + + + + + + + + Always + + + + diff --git a/Day09 - Mirage Maintenance/Day09Solver.cs b/Day09 - Mirage Maintenance/Day09Solver.cs new file mode 100644 index 0000000..2e0d766 --- /dev/null +++ b/Day09 - Mirage Maintenance/Day09Solver.cs @@ -0,0 +1,52 @@ +using AdventOfCode.Abstractions; +using AdventOfCode.Year2023.Day09.Puzzle.Oasis; + +namespace AdventOfCode.Year2023.Day09; + +public sealed class Day09Solver : DaySolver +{ + public override int Year => 2023; + public override int Day => 9; + public override string Title => "Mirage Maintenance"; + + private readonly ReportReader _reportReader; + private Report? _report; + + public Day09Solver(Day09SolverOptions options) : base(options) + { + _reportReader = new(); + } + + public Day09Solver(Action configure) + : this(DaySolverOptions.FromConfigureAction(configure)) + { + } + + public Day09Solver() : this(new Day09SolverOptions()) + { + } + + public override string SolvePart1() + { + _report ??= _reportReader.ReadReport(InputLines); + + int result = _report + .Histories + .Select(h => h.PredictNextValue()) + .Sum(); + + return result.ToString(); + } + + public override string SolvePart2() + { + _report ??= _reportReader.ReadReport(InputLines); + + int result = _report + .Histories + .Select(h => h.ExtrapolatePreviousValue()) + .Sum(); + + return result.ToString(); + } +} diff --git a/Day09 - Mirage Maintenance/Day09SolverOptions.cs b/Day09 - Mirage Maintenance/Day09SolverOptions.cs new file mode 100644 index 0000000..c33343a --- /dev/null +++ b/Day09 - Mirage Maintenance/Day09SolverOptions.cs @@ -0,0 +1,7 @@ +using AdventOfCode.Abstractions; + +namespace AdventOfCode.Year2023.Day09; + +public sealed class Day09SolverOptions : DaySolverOptions +{ +} diff --git a/Day09 - Mirage Maintenance/Program.cs b/Day09 - Mirage Maintenance/Program.cs new file mode 100644 index 0000000..86e7ee7 --- /dev/null +++ b/Day09 - Mirage Maintenance/Program.cs @@ -0,0 +1,45 @@ +using System.Diagnostics; +using AdventOfCode; +using AdventOfCode.Year2023.Day09; + +try +{ + string? filepath = args.Length switch + { + 0 => null, + 1 => args[0], + _ => throw new CommandLineException( + $"Program was called with too many arguments. Proper usage: \"dotnet run []\"." + ) + }; + + Day09Solver solver = new(options => + { + options.InputFilepath = filepath ?? options.InputFilepath; + }); + + Console.WriteLine($"--- Day {solver.Day}: {solver.Title} ---"); + + Console.Write("Part one: "); + string part1 = solver.SolvePart1(); + Console.WriteLine(part1); + + Console.Write("Part two: "); + string part2 = solver.SolvePart2(); + Console.WriteLine(part2); +} +catch (AdventOfCodeException e) +{ + string errorPrefix = e switch + { + CommandLineException => "Command line error", + InputException => "Input error", + DaySolverException => "Day solver error", + _ => throw new UnreachableException($"Unknown exception type \"{e.GetType()}\".") + }; + + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine($"{errorPrefix}: {e.Message}"); + Console.ResetColor(); + Environment.Exit(1); +} diff --git a/Day09 - Mirage Maintenance/Puzzle/Oasis/Report.cs b/Day09 - Mirage Maintenance/Puzzle/Oasis/Report.cs new file mode 100644 index 0000000..d80daea --- /dev/null +++ b/Day09 - Mirage Maintenance/Puzzle/Oasis/Report.cs @@ -0,0 +1,8 @@ +namespace AdventOfCode.Year2023.Day09.Puzzle.Oasis; + +internal sealed class Report(IEnumerable valueHistories) +{ + private readonly List _histories = valueHistories.ToList(); + + public IReadOnlyList Histories => _histories; +} diff --git a/Day09 - Mirage Maintenance/Puzzle/Oasis/ReportReader.cs b/Day09 - Mirage Maintenance/Puzzle/Oasis/ReportReader.cs new file mode 100644 index 0000000..80cc90b --- /dev/null +++ b/Day09 - Mirage Maintenance/Puzzle/Oasis/ReportReader.cs @@ -0,0 +1,30 @@ +using AdventOfCode.Common.SpanExtensions; + +namespace AdventOfCode.Year2023.Day09.Puzzle.Oasis; + +internal sealed class ReportReader +{ + public Report ReadReport(IEnumerable inputLines) + { + var valueHistories = inputLines.Select(l => ReadReportLine(l)); + return new Report(valueHistories); + } + + private static ValueHistory ReadReportLine(ReadOnlySpan line) + { + line = line.Trim(); + int valueCount = line.Count(' ') + 1; + var values = new List(valueCount); + foreach (var numberSpan in line.Split(' ')) + { + if (!int.TryParse(numberSpan, out int number)) + { + throw new InputException($"Invalid number format '{numberSpan}'."); + } + + values.Add(number); + } + + return new ValueHistory(values); + } +} diff --git a/Day09 - Mirage Maintenance/Puzzle/Oasis/ValueHistory.cs b/Day09 - Mirage Maintenance/Puzzle/Oasis/ValueHistory.cs new file mode 100644 index 0000000..cab0940 --- /dev/null +++ b/Day09 - Mirage Maintenance/Puzzle/Oasis/ValueHistory.cs @@ -0,0 +1,91 @@ +using System.Collections; + +namespace AdventOfCode.Year2023.Day09.Puzzle.Oasis; + +internal sealed class ValueHistory(IEnumerable values) + : IReadOnlyList +{ + private readonly int[] _valueHistory = values.ToArray(); + private int? _previousValue; + private int? _nextValue; + + public int Count => _valueHistory.Length; + + public int this[int index] => _valueHistory[index]; + + public IEnumerator GetEnumerator() => _valueHistory.AsEnumerable().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int PredictNextValue() + { + _nextValue ??= InternalPredictNextValue(); + return _nextValue.Value; + } + + public int ExtrapolatePreviousValue() + { + _previousValue ??= InternalExtrapolatePreviousValue(); + return _previousValue.Value; + } + + private int InternalPredictNextValue() + { + Span span = stackalloc int[Count]; + _valueHistory.CopyTo(span); + + int end = Count; + + while (true) + { + end--; + int anyValueNonZero = 0; + for (int i = 0; i < end; i++) + { + int diff = span[i + 1] - span[i]; + anyValueNonZero |= diff; + span[i] = diff; + } + + if (anyValueNonZero == 0) break; + } + + while (end < Count) + { + span[end] += span[end - 1]; + end++; + } + + return span[^1]; + } + + private int InternalExtrapolatePreviousValue() + { + Span span = stackalloc int[Count]; + _valueHistory.CopyTo(span); + + int start = -1; + + while (true) + { + start++; + int anyValueNonZero = 0; + for (int i = span.Length - 1; i > start; i--) + { + int diff = span[i] - span[i - 1]; + anyValueNonZero |= diff; + span[i] = diff; + } + + if (anyValueNonZero == 0) break; + } + + while (start >= 0) + { + span[start] -= span[start + 1]; + start--; + } + + + return span[0]; + } +} diff --git a/Day09 - Mirage Maintenance/README.md b/Day09 - Mirage Maintenance/README.md new file mode 100644 index 0000000..0ddfe91 --- /dev/null +++ b/Day09 - Mirage Maintenance/README.md @@ -0,0 +1,5 @@ +# [Day 9: Mirage Maintenance](https://adventofcode.com/2023/day/9) + +You analyze environmental data from an oasis using the Oasis And Sand Instability Sensor (OASIS). +The sensor provides a report of values and their changes over time. +You're tasked with predicting the next value in each history by analyzing the differences between consecutive values and extrapolating. diff --git a/Tests/Day09Tests.cs b/Tests/Day09Tests.cs new file mode 100644 index 0000000..4920130 --- /dev/null +++ b/Tests/Day09Tests.cs @@ -0,0 +1,25 @@ +using AdventOfCode.Year2023.Day09; + +namespace AdventOfCode.Year2023.Tests; + +[Trait("Year", "2023")] +[Trait("Day", "09")] +[Trait("Day", "9")] +public sealed class Day09Tests : BaseDayTests +{ + protected override string DayInputsDirectory => "Day09"; + + protected override Day09Solver CreateSolver(Day09SolverOptions options) => new(options); + + [Theory] + [InlineData("example-input.txt", "114")] + [InlineData("my-input.txt", "1969958987")] + public void TestPart1(string inputFilename, string expectedResult, Day09SolverOptions? options = null) + => BaseTestPart1(inputFilename, expectedResult, options); + + [Theory] + [InlineData("example-input.txt", "2")] + [InlineData("my-input.txt", "1068")] + public void TestPart2(string inputFilename, string expectedResult, Day09SolverOptions? options = null) + => BaseTestPart2(inputFilename, expectedResult, options); +} diff --git a/Tests/Inputs/Day09/example-input.txt b/Tests/Inputs/Day09/example-input.txt new file mode 100644 index 0000000..929d9b1 --- /dev/null +++ b/Tests/Inputs/Day09/example-input.txt @@ -0,0 +1,3 @@ +0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45 diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index fd7f8d5..2bb78ca 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -38,6 +38,7 @@ +