-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added day 5 part 1 bare solution * Added part 2 base code and necessary changes * Added first draft of MultiRange handy for part 2 * More extensions of MultiRange class * Push forward solution held back by dysfunctional MultiRange.Remove * Fix tests after main branch merge * Added state of part 2 solution * Use newest Common package with MultiRange instead of implementing own * Add Day 5 to solved solution folder * Format
- Loading branch information
Showing
22 changed files
with
1,063 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
Day05 - If You Give A Seed A Fertilizer/.vscode/launch.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/Day05.dll", | ||
"args": [ | ||
"input.txt" | ||
], | ||
"cwd": "${workspaceFolder}", | ||
"console": "internalConsole", | ||
"stopAtEntry": false | ||
} | ||
] | ||
} |
21 changes: 21 additions & 0 deletions
21
Day05 - If You Give A Seed A Fertilizer/.vscode/tasks.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "build", | ||
"command": "dotnet", | ||
"type": "process", | ||
"group": { | ||
"kind": "build", | ||
"isDefault": true | ||
}, | ||
"args": [ | ||
"build", | ||
"${workspaceFolder}/Day05.csproj", | ||
"/property:GenerateFullPaths=true", | ||
"/consoleloggerparameters:NoSummary" | ||
], | ||
"problemMatcher": "$msCompile" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<RootNamespace>AdventOfCode.Year2023.Day05</RootNamespace> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="mMosiur.AdventOfCode.Abstractions" Version="4.4.0" /> | ||
<PackageReference Include="mMosiur.AdventOfCode.Common" Version="0.1.2" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Update="*.txt"> | ||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | ||
</None> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
using AdventOfCode.Abstractions; | ||
using AdventOfCode.Year2023.Day05.Puzzle; | ||
|
||
namespace AdventOfCode.Year2023.Day05; | ||
|
||
public sealed class Day05Solver : DaySolver | ||
{ | ||
public override int Year => 2023; | ||
public override int Day => 5; | ||
public override string Title => "If You Give A Seed A Fertilizer"; | ||
|
||
private readonly InputReaderWithCaching _inputReader; | ||
private readonly SeedNumberCategoryCalculator _calculator; | ||
|
||
public Day05Solver(Day05SolverOptions options) : base(options) | ||
{ | ||
_inputReader = new(Input); | ||
_calculator = new(NumberCategory.Seed, NumberCategory.Location); | ||
} | ||
|
||
public Day05Solver(Action<Day05SolverOptions> configure) | ||
: this(DaySolverOptions.FromConfigureAction(configure)) | ||
{ | ||
} | ||
|
||
public Day05Solver() : this(new Day05SolverOptions()) | ||
{ | ||
} | ||
|
||
public override string SolvePart1() | ||
{ | ||
var almanac = _inputReader.ReadInputSingleSeeds(); | ||
var locationNumbers = _calculator.CalculateTargetCategoryNumbers(almanac); | ||
uint minLocationNumber = locationNumbers.Min(); | ||
return minLocationNumber.ToString(); | ||
} | ||
|
||
public override string SolvePart2() | ||
{ | ||
var almanac = _inputReader.ReadInputSeedRanges(); | ||
var locationNumbers = _calculator.CalculateTargetCategoryNumbers(almanac); | ||
uint minLocationNumber = locationNumbers.Select(r => r.Start).Min(); | ||
return minLocationNumber.ToString(); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
Day05 - If You Give A Seed A Fertilizer/Day05SolverOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using AdventOfCode.Abstractions; | ||
|
||
namespace AdventOfCode.Year2023.Day05; | ||
|
||
public sealed class Day05SolverOptions : DaySolverOptions | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
global using Range = AdventOfCode.Common.Numerics.Interval<uint>; | ||
global using MultiRange = AdventOfCode.Common.Numerics.MultiInterval<uint>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
using System.Diagnostics; | ||
using AdventOfCode; | ||
using AdventOfCode.Year2023.Day05; | ||
|
||
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 [<input filepath>]\"." | ||
) | ||
}; | ||
|
||
Day05Solver 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace AdventOfCode.Year2023.Day05.Puzzle; | ||
|
||
internal abstract class Almanac<TSeedNumbersType> | ||
{ | ||
private readonly Dictionary<NumberCategory, NumberMap> _numberMapsBySourceCategory; | ||
|
||
public abstract TSeedNumbersType SeedNumbers { get; } | ||
public IReadOnlyCollection<NumberMap> NumberMaps { get; } | ||
|
||
protected Almanac(IReadOnlyCollection<NumberMap> numberMaps) | ||
{ | ||
NumberMaps = numberMaps; | ||
_numberMapsBySourceCategory = NumberMaps.ToDictionary(nm => nm.SourceCategory); | ||
} | ||
|
||
public NumberMap this[NumberCategory sourceCategory] => _numberMapsBySourceCategory[sourceCategory]; | ||
} |
12 changes: 12 additions & 0 deletions
12
Day05 - If You Give A Seed A Fertilizer/Puzzle/AlmanacSeedRanges.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace AdventOfCode.Year2023.Day05.Puzzle; | ||
|
||
internal sealed class AlmanacSeedRanges : Almanac<MultiRange> | ||
{ | ||
public override MultiRange SeedNumbers { get; } | ||
|
||
public AlmanacSeedRanges(MultiRange seedNumbers, IReadOnlyCollection<NumberMap> numberMaps) | ||
: base(numberMaps) | ||
{ | ||
SeedNumbers = seedNumbers; | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
Day05 - If You Give A Seed A Fertilizer/Puzzle/AlmanacSingleSeeds.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace AdventOfCode.Year2023.Day05.Puzzle; | ||
|
||
internal sealed class AlmanacSingleSeeds : Almanac<IReadOnlyCollection<uint>> | ||
{ | ||
public override IReadOnlyCollection<uint> SeedNumbers { get; } | ||
|
||
public AlmanacSingleSeeds(IReadOnlyCollection<uint> seedNumbers, IReadOnlyCollection<NumberMap> numberMaps) | ||
: base(numberMaps) | ||
{ | ||
SeedNumbers = seedNumbers; | ||
} | ||
} |
159 changes: 159 additions & 0 deletions
159
Day05 - If You Give A Seed A Fertilizer/Puzzle/InputReaderWithCaching.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text.RegularExpressions; | ||
using AdventOfCode.Common.SpanExtensions; | ||
|
||
namespace AdventOfCode.Year2023.Day05.Puzzle; | ||
|
||
internal sealed class InputReaderWithCaching | ||
{ | ||
private static readonly string[] NewLines = { "\r\n", "\r", "\n" }; | ||
private static readonly string[] DoubleNewLines = NewLines.Select(nl => $"{nl}{nl}").ToArray(); | ||
private static readonly Regex SeedNumbersRegex = new(@"^seeds: ([\d ]+)$", RegexOptions.Compiled); | ||
private static readonly Regex NumberMapHeaderRegex = new(@"^(?<SourceNumberCategory>\w+)-to-(?<DestinationNumberCategory>\w+) map:$", RegexOptions.Compiled); | ||
|
||
private readonly string _input; | ||
|
||
private IReadOnlyList<uint>? _almanacSeedNumbers; | ||
private IReadOnlyList<NumberMap>? _almanacNumberMaps; | ||
private AlmanacSingleSeeds? _almanacSingleSeeds; | ||
private AlmanacSeedRanges? _almanacSeedRanges; | ||
|
||
public InputReaderWithCaching(string input) | ||
{ | ||
_input = input; | ||
} | ||
|
||
private static IReadOnlyList<uint> ReadSeedNumbers(string input) | ||
{ | ||
var match = SeedNumbersRegex.Match(input); | ||
if (!match.Success) | ||
{ | ||
throw new InputException($"""Seed numbers line was in an unrecognized format ("{input}")."""); | ||
} | ||
|
||
var seedsSpan = match.Groups[1].ValueSpan; | ||
List<uint> seedNumbers = new(seedsSpan.Count(' ') + 1); | ||
foreach (var seedNumberSpan in seedsSpan.Split(' ')) | ||
{ | ||
if (!uint.TryParse(seedNumberSpan, out uint seedNumber)) | ||
{ | ||
throw new InputException($"""Could not parse a number in a seed number line ("{seedNumberSpan}")."""); | ||
} | ||
|
||
seedNumbers.Add(seedNumber); | ||
} | ||
|
||
return seedNumbers; | ||
} | ||
|
||
private static (NumberCategory SourceCategory, NumberCategory DestinationCategory) ReadNumberMapHeader(string header) | ||
{ | ||
var match = NumberMapHeaderRegex.Match(header); | ||
if (!match.Success) | ||
{ | ||
throw new InputException($"""Number map header was in an unrecognized format ("{header}")."""); | ||
} | ||
|
||
return ( | ||
SourceCategory: Enum.Parse<NumberCategory>(match.Groups["SourceNumberCategory"].ValueSpan, ignoreCase: true), | ||
DestinationCategory: Enum.Parse<NumberCategory>(match.Groups["DestinationNumberCategory"].ValueSpan, ignoreCase: true) | ||
); | ||
} | ||
|
||
private static NumberMapLine ReadNumberMapLine(ReadOnlySpan<char> lineSpan) | ||
{ | ||
if (!lineSpan.TrySplitInThree( | ||
separator: ' ', | ||
out var firstNumberSpan, | ||
out var secondNumberSpan, | ||
out var thirdNumberSpan, | ||
allowMultipleSeparators: true)) | ||
{ | ||
throw new InputException($"""Could not parse number map line from input line "{lineSpan}"."""); | ||
} | ||
|
||
return new NumberMapLine( | ||
destinationRangeStart: uint.Parse(firstNumberSpan), | ||
sourceRangeStart: uint.Parse(secondNumberSpan), | ||
rangeLength: uint.Parse(thirdNumberSpan)); | ||
} | ||
|
||
private static NumberMap ReadNumberMap(string section) | ||
{ | ||
var sectionSpan = section.AsSpan(); | ||
var lineSpanIterator = sectionSpan.EnumerateLines(); | ||
if (!lineSpanIterator.MoveNext()) throw new InputException($"Could not read number map header from input section \"{section}\"."); | ||
(NumberCategory sourceCategory, NumberCategory destinationCategory) = ReadNumberMapHeader(lineSpanIterator.Current.ToString()); | ||
var numberMapLines = new List<NumberMapLine>(sectionSpan.Count('\n')); | ||
while (lineSpanIterator.MoveNext()) | ||
{ | ||
var numberMapLine = ReadNumberMapLine(lineSpanIterator.Current); | ||
numberMapLines.Add(numberMapLine); | ||
} | ||
|
||
return new NumberMap(sourceCategory, destinationCategory, numberMapLines); | ||
} | ||
|
||
[MemberNotNull(nameof(_almanacSeedNumbers))] | ||
[MemberNotNull(nameof(_almanacNumberMaps))] | ||
private void InternalReadInput() | ||
{ | ||
if (_almanacSeedNumbers is not null && _almanacNumberMaps is not null) | ||
{ | ||
return; | ||
} | ||
|
||
string[] result = _input.Split(DoubleNewLines, StringSplitOptions.TrimEntries); | ||
string seedNumbersRow = result[0]; | ||
_almanacSeedNumbers = ReadSeedNumbers(seedNumbersRow); | ||
|
||
var numberMaps = new List<NumberMap>(result.Length - 1); | ||
for (int i = 1; i < result.Length; i++) | ||
{ | ||
string mapSectionLine = result[i]; | ||
var mapSection = ReadNumberMap(mapSectionLine); | ||
numberMaps.Add(mapSection); | ||
} | ||
|
||
_almanacNumberMaps = numberMaps; | ||
} | ||
|
||
private AlmanacSingleSeeds BuildAlmanacSingleSeeds() | ||
{ | ||
InternalReadInput(); | ||
return new(_almanacSeedNumbers, _almanacNumberMaps); | ||
} | ||
|
||
private AlmanacSeedRanges BuildAlmanacSeedRanges() | ||
{ | ||
InternalReadInput(); | ||
if (_almanacSeedNumbers.Count % 2 != 0) | ||
{ | ||
throw new InputException("Seed number count must be even to interpret them as ranges."); | ||
} | ||
|
||
|
||
var seedNumberRanges = new MultiRange(_almanacSeedNumbers.Count / 2); | ||
for (int i = 0; i < _almanacSeedNumbers.Count; i += 2) | ||
{ | ||
uint rangeStart = _almanacSeedNumbers[i]; | ||
uint rangeLength = _almanacSeedNumbers[i + 1]; | ||
var range = new Range(rangeStart, rangeStart + rangeLength - 1); | ||
seedNumberRanges.Add(range); | ||
} | ||
|
||
return new(seedNumberRanges, _almanacNumberMaps); | ||
} | ||
|
||
[MemberNotNull(nameof(_almanacSingleSeeds))] | ||
public AlmanacSingleSeeds ReadInputSingleSeeds() | ||
{ | ||
return _almanacSingleSeeds ??= BuildAlmanacSingleSeeds(); | ||
} | ||
|
||
[MemberNotNull(nameof(_almanacSeedRanges))] | ||
public AlmanacSeedRanges ReadInputSeedRanges() | ||
{ | ||
return _almanacSeedRanges ??= BuildAlmanacSeedRanges(); | ||
} | ||
} |
Oops, something went wrong.