feat: solve puzzle 3

This commit is contained in:
Stevan Freeborn
2023-12-03 22:54:52 -06:00
parent 575b287b50
commit 19c35bc946
11 changed files with 777 additions and 19 deletions
+4 -4
View File
@@ -9,16 +9,16 @@ public class Program
if (args.Length is 0) if (args.Length is 0)
{ {
Console.WriteLine("Please provide a path to the input file."); Console.WriteLine("Please provide a path to the input file.");
return 1; return -1;
} }
if (File.Exists(args[0]) is false) if (File.Exists(args[0]) is false)
{ {
Console.WriteLine("The provided file does not exist."); Console.WriteLine("The provided file does not exist.");
return 2; return -2;
} }
IPuzzleSolver puzzleSolver = args.Length > 1 && args[1] == "2" IPuzzleSolver puzzleSolver = args.Length > 1 && args[1] == "part2"
? new PartTwoPuzzleSolver() ? new PartTwoPuzzleSolver()
: new PartOnePuzzleSolver(); : new PartOnePuzzleSolver();
@@ -27,7 +27,7 @@ public class Program
Console.WriteLine($"The sum of all calibration values is {result}."); Console.WriteLine($"The sum of all calibration values is {result}.");
return 0; return result;
} }
} }
+7
View File
@@ -26,7 +26,14 @@ public class Program
? puzzleSolver.SumGameMinimumPowers(games) ? puzzleSolver.SumGameMinimumPowers(games)
: puzzleSolver.SumPossibleGameIds(games); : puzzleSolver.SumPossibleGameIds(games);
if (args.Length > 1 && args[1] == "part2")
{
Console.WriteLine($"The sum of the minimum powers of all games is {result}.");
}
else
{
Console.WriteLine($"The sum of all possible game ids is {result}."); Console.WriteLine($"The sum of all possible game ids is {result}.");
}
return result; return result;
} }
-1
View File
@@ -1 +0,0 @@
global using Xunit;
-10
View File
@@ -1,10 +0,0 @@
namespace GearRatios.Test;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
@@ -10,6 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+2
View File
@@ -0,0 +1,2 @@
global using Xunit;
global using FluentAssertions;
+353
View File
@@ -0,0 +1,353 @@
namespace GearRatios.Test;
public class LineTests
{
[Theory, MemberData(nameof(TestData.LineData), MemberType = typeof(TestData))]
public void Parse_GivenLine_ItShouldParseExpectedValues(string line, Line expected)
{
var actual = Line.Parse(line);
actual.Should().BeEquivalentTo(expected);
}
[Theory, MemberData(nameof(TestData.PartNumberData), MemberType = typeof(TestData))]
public void GetPartNumbers_GivenLine_ItShouldReturnExpectedValues(Line currentLine, Line? prevLine, Line? nextLine, List<int> expected)
{
var actual = currentLine.GetPartNumbers(prevLine, nextLine);
actual.Should().BeEquivalentTo(expected);
}
[Theory, MemberData(nameof(TestData.GearData), MemberType = typeof(TestData))]
public void GetGearRatios_GivenLine_ItShouldReturnExpectedValues(Line currentLine, Line? prevLine, Line? nextLine, List<int> expected)
{
var actual = currentLine.GetGearRatios(prevLine, nextLine);
actual.Should().BeEquivalentTo(expected);
}
public static class TestData
{
public static IEnumerable<object?[]> GearData =>
new List<object?[]>
{
new object?[]
{
new Line(
[],
[]
),
null,
null,
new List<int>(),
},
new object?[]
{
new Line(
[
new()
{
{ 467, new Indexes(0, 2) },
},
new()
{
{ 114, new Indexes(5, 7) },
},
],
[
]
),
null,
null,
new List<int>(),
},
new object?[]
{
new Line(
[],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
null,
null,
new List<int>(),
},
new object?[]
{
new Line(
[
new()
{
{ 617, new Indexes(0, 2) },
{ 114, new Indexes(4, 6) },
},
],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
null,
null,
new List<int> { 617 * 114 },
},
new object?[]
{
new Line(
[],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
new Line(
[
new()
{
{ 467, new Indexes(0, 2) },
},
new()
{
{ 114, new Indexes(5, 7) },
},
],
[]
),
new Line(
[
new()
{
{ 35, new Indexes(2, 3) },
},
new()
{
{ 633, new Indexes(6, 8) },
},
],
[]
),
new List<int> { 467 * 35 },
}
};
public static IEnumerable<object?[]> PartNumberData =>
new List<object?[]>
{
new object?[]
{
new Line(
[
new()
{
{ 467, new Indexes(0, 2) },
},
new()
{
{ 114, new Indexes(5, 7) },
},
],
[
]
),
null,
null,
new List<int>(),
},
new object?[]
{
new Line(
[],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
null,
null,
new List<int>(),
},
new object?[]
{
new Line(
[
new()
{
{ 617, new Indexes(0, 2) },
},
],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
null,
null,
new List<int> { 617 },
},
new object?[]
{
new Line(
[
new()
{
{ 617, new Indexes(0, 2) },
},
new()
{
{ 114, new Indexes(6, 8) },
},
],
[
new()
{
{ "*", new Indexes(3, 3) },
},
new()
{
{ "*", new Indexes(9, 9) },
},
]
),
null,
null,
new List<int> { 617, 114 },
},
new object?[]
{
new Line(
[
new()
{
{ 467, new Indexes(0, 2) },
},
new()
{
{ 114, new Indexes(5, 7) },
},
],
[
]
),
null,
new Line(
[],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
new List<int>() { 467 },
},
new object?[]
{
new Line(
[
new()
{
{ 58, new Indexes(7, 8) },
},
],
[
new()
{
{ "+", new Indexes(5, 5) },
},
]
),
new Line(
[
new()
{
{ 617, new Indexes(0, 2) },
},
],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
new Line(
[
new()
{
{ 592, new Indexes(2, 4) },
},
],
[]
),
new List<int>(),
},
};
public static IEnumerable<object[]> LineData =>
new List<object[]>
{
new object[]
{
"467..114..",
new Line(
[
new()
{
{ 467, new Indexes(0, 2) },
},
new()
{
{ 114, new Indexes(5, 7) },
},
],
[]
),
},
new object[]
{
"...*......",
new Line(
[],
[
new()
{
{ "*", new Indexes(3, 3) },
},
]
),
},
new object[]
{
"...41+...564",
new Line(
[
new()
{
{ 41, new Indexes(3, 4) },
},
new()
{
{ 564, new Indexes(9, 11) },
},
],
[
new()
{
{ "+", new Indexes(5, 5) },
},
]
),
}
};
};
}
+90
View File
@@ -0,0 +1,90 @@
namespace GearRatios.Test;
public class PuzzleSolverTests
{
private readonly PuzzleSolver _sut = new();
[Theory]
[MemberData(nameof(TestData.SumPartNumbersData), MemberType = typeof(TestData))]
public void SumPartNumbers_GivenSchematic_ItShouldReturnExpectedValue(string[] schematic, int expected)
{
var actual = _sut.SumPartNumbers(schematic);
actual.Should().Be(expected);
}
[Theory]
[MemberData(nameof(TestData.SumGearRatios), MemberType = typeof(TestData))]
public void SumGearRatios_GivenSchematic_ItShouldReturnExpectedValue(string[] schematic, int expected)
{
var actual = _sut.SumGearRatios(schematic);
actual.Should().Be(expected);
}
public static class TestData
{
private static readonly string[] TestSchematic =
[
"467..114..",
"...*......",
"..35..633.",
"......#...",
"617*......",
".....+.58.",
"..592.....",
"......755.",
"...$.*....",
".664.598..",
];
public static IEnumerable<object[]> SumGearRatios =>
new List<object[]>
{
new object[]
{
TestSchematic,
467835,
},
};
public static IEnumerable<object?[]> SumPartNumbersData =>
new List<object?[]>
{
new object?[]
{
TestSchematic,
4361,
},
new object[]
{
new[]
{
"...................305.124................................432..............................................576..313.....514.................",
".............113...-......&....................&...819...........654..../..........................&901................*....869.257.........",
"...377..&783../.................................9...........855*......940..463................-.........................844.*....@......679.",
"......*...........197.261.....817..336.759............&742......548.......&........748......844.............#.......&........254...169..*...",
},
10421,
},
new object[]
{
new[]
{
".............................457....834.....................94..564..........&.........498....*...304....../...*.....*..........845.....&...",
".......12*48.753...244..196....=......$..721....90*29.........*.................+.522.......487...............317.....531..311........20....",
".177...............*.....*.................*.............*969.611.......*565..338...*.............712..922$.......770......*....522.........",
},
8318
},
new object[]
{
new[]
{
"663..462...........*..........109..706*.................=.............553........712......*971......674.396...635*..........................",
"........../...728.952...413......*......744.......%..........................300..*....782................*.......742..&424........41+...564",
"........375...%.........*......450.456.$.........714........851.327..#...+......*...+.......179.630....854.................................*",
},
11612
}
};
}
}
+228 -2
View File
@@ -1,2 +1,228 @@
// See https://aka.ms/new-console-template for more information namespace GearRatios;
Console.WriteLine("Hello, World!");
public class Program
{
public async static Task<int> Main(string[] args)
{
if (args.Length is 0)
{
Console.WriteLine("Please provide a path to the input file.");
return -1;
}
if (File.Exists(args[0]) is false)
{
Console.WriteLine("The provided file does not exist.");
return -2;
}
var puzzleSolver = new PuzzleSolver();
var input = await File.ReadAllLinesAsync(args[0]);
var result = args.Length > 1 && args[1] == "part2"
? puzzleSolver.SumGearRatios(input)
: puzzleSolver.SumPartNumbers(input);
if (args.Length > 1 && args[1] == "part2")
{
Console.WriteLine($"The sum of all gear ratios is {result}.");
}
else
{
Console.WriteLine($"The sum of all part numbers is {result}.");
}
return result;
}
}
public class PuzzleSolver
{
public int SumPartNumbers(string[] schematic)
{
var allPartNumbers = new List<int>();
for (var i = 0; i < schematic.Length; i++)
{
var currentLine = Line.Parse(schematic[i]);
var prevLine = i > 0 ? Line.Parse(schematic[i - 1]) : null;
var nextLine = i < schematic.Length - 1 ? Line.Parse(schematic[i + 1]) : null;
var linePartNumbers = currentLine.GetPartNumbers(prevLine, nextLine);
allPartNumbers.AddRange(linePartNumbers);
}
return allPartNumbers.Sum();
}
public int SumGearRatios(string[] schematic)
{
var allGearRatios = new List<int>();
for (var i = 0; i < schematic.Length; i++)
{
var currentLine = Line.Parse(schematic[i]);
var prevLine = i > 0 ? Line.Parse(schematic[i - 1]) : null;
var nextLine = i < schematic.Length - 1 ? Line.Parse(schematic[i + 1]) : null;
var lineGearRatios = currentLine.GetGearRatios(prevLine, nextLine);
allGearRatios.AddRange(lineGearRatios);
}
return allGearRatios.Sum();
}
}
public class Line(
List<Dictionary<int, Indexes>> numbers,
List<Dictionary<string, Indexes>> specialCharacters
)
{
public List<Dictionary<int, Indexes>> Numbers { get; init; } = numbers;
public List<Dictionary<string, Indexes>> SpecialCharacters { get; init; } = specialCharacters;
public List<int> SpecialCharacterIndexes => SpecialCharacters.SelectMany(dict => dict.Values).Select(v => v.Start).ToList();
private bool IsGearCharacter(string character) => character == "*";
public List<int> GetGearRatios(Line? previousLine = null, Line? nextLine = null)
{
var gearRatios = new List<int>();
var gearCharacters = SpecialCharacters.Where(dict => dict.Keys.Any(IsGearCharacter)).ToList();
if (previousLine is not null)
{
Numbers.AddRange(previousLine.Numbers);
}
if (nextLine is not null)
{
Numbers.AddRange(nextLine.Numbers);
}
foreach (var gear in gearCharacters)
{
foreach (var (key, value) in gear)
{
var beforeGear = value.Start - 1;
var afterGear = value.End + 1;
var gearNumbers = Numbers
.Where(
n => n.Values.Any(
v =>
v.Start >= beforeGear && v.Start <= afterGear ||
v.End >= beforeGear && v.End <= afterGear
)
)
.SelectMany(n => n.Keys)
.ToList();
if (gearNumbers.Count == 2)
{
gearRatios.Add(gearNumbers.First() * gearNumbers.Last());
}
}
}
return gearRatios;
}
public List<int> GetPartNumbers(Line? previousLine = null, Line? nextLine = null)
{
var partNumbers = new List<int>();
if (previousLine is not null)
{
SpecialCharacters.AddRange(previousLine.SpecialCharacters);
}
if (nextLine is not null)
{
SpecialCharacters.AddRange(nextLine.SpecialCharacters);
}
foreach (var number in Numbers)
{
foreach (var (key, value) in number)
{
var beforeNumber = value.Start - 1;
var afterNumber = value.End + 1;
if (SpecialCharacterIndexes.Any(idx => idx >= beforeNumber && idx <= afterNumber))
{
partNumbers.Add(key);
}
}
}
return partNumbers;
}
public static Line Parse(string line)
{
var numbers = new List<Dictionary<int, Indexes>>();
var specialCharacters = new List<Dictionary<string, Indexes>>();
var number = string.Empty;
int? numberStart = null;
int? numberEnd;
for (var i = 0; i < line.Length; i++)
{
var currentCharacter = line[i];
if (char.IsDigit(currentCharacter))
{
number += line[i];
numberStart ??= i;
if (i == line.Length - 1)
{
numberEnd = i;
numbers.Add(new()
{
{
int.Parse(number),
new Indexes(numberStart.Value, numberEnd.Value)
}
});
}
}
else
{
if (numberStart.HasValue)
{
numberEnd = i - 1;
numbers.Add(new()
{
{
int.Parse(number),
new Indexes(numberStart.Value, numberEnd.Value)
}
});
number = string.Empty;
numberStart = null;
}
if (currentCharacter == '.')
{
continue;
}
specialCharacters.Add(new()
{
{
currentCharacter.ToString(),
new Indexes(i, i)
}
});
}
}
return new Line(numbers, specialCharacters);
}
}
public class Indexes(int start, int end)
{
public int Start { get; init; } = start;
public int End { get; init; } = end;
}
+90
View File
@@ -0,0 +1,90 @@
# Notes
## Contraints
- Periods don't count as special characters
- All lines have same length
- If not a number or period then it is a special character
Each line parsed need to collect all numbers.
Can't know whether a collected number is a part number until we
have also parsed the proceeding or following line.
```text
467..114..
...*......
..35..633.
......#...
617*......
```
After parsing first line we would have `467` with start index 0 and end index 2 and `114` with start index 5 and end index 7.
For 467 to be a part number it needs to have a special character at index 3 or it needs to have a special character at index 1, 2, 3, 4 on the next line.
For 114 to be a part number it needs to have a special character at index 4 or index 8 or it needs to have a special character at index 4, 5, 6, 7 on the next line.
Need to keep track of:
- Previous line - will start as empty
- Current line
- Next line
So you look at the current line and you parse all the numbers with their start and end index. You also parse all special characters with their index.
Then you end up with something like this: currentLine
```json
{
"numbers": [
"467": {
"start": 0,
"end": 2
},
"114": {
"start": 5,
"end": 7
}
],
"specialCharacters": []
}
```
```csharp
var currentLineString = "467..114..";
var currentLine = new Line(currentLineString);
class Line
{
public List<Dictionary<int, Indexes>> Numbers { get; set; }
public List<Dictionary<int, Indexes>> SpecialCharacters { get; set; }
public Line(string line)
{
// parse line
}
}
public class Indexes
{
public int Start { get; set; }
public int End { get; set; }
public Indexes(int start, int length)
{
Start = start;
End = start + length;
}
}
```
Then you see if those numbers are part numbers just by checking if a special character proceeds or follows their start and end indexes.
```csharp
```
If not then you need to see if there is a previous line and whether there is a special character at on the previous line that has an index that is between 1 less than the start index and 1 more than the end index.
If not then you need to see if there is a next line and whether there is a special character at on the next line that has an index that is between 1 less than the start index and 1 more than the end index.
```
+1 -1
View File
@@ -19,7 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03", "03", "{EA04152D-316A-
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearRatios", "03\GearRatios\GearRatios.csproj", "{F697AC19-C92D-4E2A-975A-1A16A1961133}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearRatios", "03\GearRatios\GearRatios.csproj", "{F697AC19-C92D-4E2A-975A-1A16A1961133}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearRatios.Test", "03\GearRatios.Test\GearRatios.Test.csproj", "{FEE9253C-A56B-4735-BDB3-C62220C5B1B1}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GearRatios.Tests", "03\GearRatios.Tests\GearRatios.Tests.csproj", "{FEE9253C-A56B-4735-BDB3-C62220C5B1B1}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution