feat: solve part 2

This commit is contained in:
Stevan Freeborn
2023-12-21 12:27:34 -06:00
parent e18b944893
commit 786189b914
4 changed files with 417 additions and 40 deletions
+133 -9
View File
@@ -18,23 +18,147 @@ public class DishTests
result.Should().Be(expected);
}
[Theory]
[MemberData(nameof(TestData.CalculateTotalLoadWithSpinCycleData), MemberType = typeof(TestData))]
public void CalculateTotalLoad_WhenCalledWithSpinCycle_ItShouldReturnExpectedLoad(Dish dish, long expected)
{
var result = dish.CalculateTotalLoad(true);
result.Should().Be(expected);
}
[Theory]
[MemberData(nameof(TestData.TiltTestData), MemberType = typeof(TestData))]
public void TiltDish_WhenCalled_ItShouldReturnExpectedDish(Dish dish, int times, Dish expected)
{
var result = dish.TiltDish(times);
result.Should().BeEquivalentTo(expected);
}
[Fact]
public void CalculateTotalLoad_WhenCalledWithExample_ItShouldReturnExpectedLoad()
{
var input = File.ReadAllLines("EXAMPLE.txt");
var dish = Dish.Parse(input);
var result = dish.CalculateTotalLoad();
result.Should().Be(136);
}
[Fact]
public void CalculateTotalLoad_WhenCalledWithInput_ItShouldReturnExpectedLoad()
{
var input = File.ReadAllLines("INPUT.txt");
var dish = Dish.Parse(input);
var result = dish.CalculateTotalLoad();
result.Should().Be(106997);
}
[Fact]
public void CalculateTotalLoad_WhenCalledWithExampleAndSpinCycle_ItShouldReturnExpectedLoad()
{
var input = File.ReadAllLines("EXAMPLE.txt");
var dish = Dish.Parse(input);
var result = dish.CalculateTotalLoad(true);
result.Should().Be(64);
}
[Fact]
public void CalculateTotalLoad_WhenCalledWithInputAndSpinCycle_ItShouldReturnExpectedLoad()
{
var input = File.ReadAllLines("INPUT.txt");
var dish = Dish.Parse(input);
var result = dish.CalculateTotalLoad(true);
result.Should().Be(99641);
}
public static class TestData
{
private static readonly Dish TestDish = new(
[
['O', 'O', 'O', 'O', '.', '#', '.', 'O', '.', '.'],
['O', 'O', '.', '.', '#', '.', '.', '.', '.', '#'],
['O', 'O', '.', '.', 'O', '#', '#', '.', '.', 'O'],
['O', '.', '.', '#', '.', 'O', 'O', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '#', '.'],
['.', '.', '#', '.', '.', '.', '.', '#', '.', '#'],
['.', '.', 'O', '.', '.', '#', '.', 'O', '.', 'O'],
['.', '.', 'O', '.', '.', '.', '.', '.', '.', '.'],
['O', '.', '.', '.', '.', '#', '.', '.', '.', '.'],
['O', '.', 'O', 'O', '#', '.', '.', '.', '.', '#'],
['.', '.', '.', '.', '.', '#', '#', '.', '.', '.'],
['O', 'O', '.', '#', 'O', '.', '.', '.', '.', 'O'],
['.', 'O', '.', '.', '.', '.', '.', 'O', '#', '.'],
['O', '.', '#', '.', '.', 'O', '.', '#', '.', '#'],
['.', '.', 'O', '.', '.', '#', 'O', '.', '.', 'O'],
['.', '.', '.', '.', '.', '.', '.', 'O', '.', '.'],
['#', '.', '.', '.', '.', '#', '#', '#', '.', '.'],
['#', '.', '.', '.', '.', '#', '.', '.', '.', '.'],
['#', 'O', 'O', '.', '.', '#', '.', '.', '.', '.'],
]
);
public static IEnumerable<object[]> TiltTestData =>
new List<object[]>
{
new object[]
{
TestDish,
1,
new Dish(
[
['.', '.', '.', '.', '.', '#', '.', '.', '.', '.'],
['.', '.', '.', '.', '#', '.', '.', '.', 'O', '#'],
['.', '.', '.', 'O', 'O', '#', '#', '.', '.', '.'],
['.', 'O', 'O', '#', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', 'O', 'O', 'O', '#', '.'],
['.', 'O', '#', '.', '.', '.', 'O', '#', '.', '#'],
['.', '.', '.', '.', 'O', '#', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', 'O', 'O', 'O', 'O'],
['#', '.', '.', '.', 'O', '#', '#', '#', '.', '.'],
['#', '.', '.', 'O', 'O', '#', '.', '.', '.', '.'],
]
)
},
new object[]
{
TestDish,
2,
new Dish(
[
['.', '.', '.', '.', '.', '#', '.', '.', '.', '.'],
['.', '.', '.', '.', '#', '.', '.', '.', 'O', '#'],
['.', '.', '.', '.', '.', '#', '#', '.', '.', '.'],
['.', '.', 'O', '#', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', 'O', 'O', 'O', '#', '.'],
['.', 'O', '#', '.', '.', '.', 'O', '#', '.', '#'],
['.', '.', '.', '.', 'O', '#', '.', '.', '.', 'O'],
['.', '.', '.', '.', '.', '.', '.', 'O', 'O', 'O'],
['#', '.', '.', 'O', 'O', '#', '#', '#', '.', '.'],
['#', '.', 'O', 'O', 'O', '#', '.', '.', '.', 'O'],
]
),
},
new object[]
{
TestDish,
3,
new Dish(
[
['.', '.', '.', '.', '.', '#', '.', '.', '.', '.'],
['.', '.', '.', '.', '#', '.', '.', '.', 'O', '#'],
['.', '.', '.', '.', '.', '#', '#', '.', '.', '.'],
['.', '.', 'O', '#', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', 'O', 'O', 'O', '#', '.'],
['.', 'O', '#', '.', '.', '.', 'O', '#', '.', '#'],
['.', '.', '.', '.', 'O', '#', '.', '.', '.', 'O'],
['.', '.', '.', '.', '.', '.', '.', 'O', 'O', 'O'],
['#', '.', '.', '.', 'O', '#', '#', '#', '.', 'O'],
['#', '.', 'O', 'O', 'O', '#', '.', '.', '.', 'O'],
]
),
}
};
public static IEnumerable<object[]> CalculateTotalLoadWithSpinCycleData =>
new List<object[]>
{
new object[]
{
TestDish,
64,
}
};
public static IEnumerable<object[]> CalculateTotalLoadTestData =>
new List<object[]>
{
@@ -27,4 +27,16 @@
<ProjectReference Include="..\ParabolicReflectorDish\ParabolicReflectorDish.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\INPUT.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="..\EXAMPLE.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
+245 -4
View File
@@ -18,6 +18,7 @@ public class Program
return -2;
}
var isPart2 = args.Length > 1 && args[1] == "part2";
var input = await File.ReadAllLinesAsync(args[0]);
var stopwatch = new Stopwatch();
@@ -25,7 +26,7 @@ public class Program
var result = Dish
.Parse(input)
.CalculateTotalLoad();
.CalculateTotalLoad(isPart2);
stopwatch.Stop();
@@ -35,6 +36,11 @@ public class Program
}
}
/// <summary>
/// Represents a parabolic reflector dish.
/// </summary>
/// <param name="rows">The rows of the dish.</param>
/// <returns>An instance of the <see cref="Dish"/> class.</returns>
public class Dish(
List<List<char>> rows
)
@@ -43,12 +49,18 @@ public class Dish(
private const char SquareRockSymbol = '#';
private const char EmptySpaceSymbol = '.';
/// <summary>
/// Gets the rows of the dish.
/// </summary>
public List<List<char>> Rows { get; init; } = rows;
private List<List<char>> TiltDishToNorth(List<List<char>> rows)
{
// create a copy of the rows which we can modify
var modifiedRows = rows.ToList();
var modifiedRows = rows
.Select(r => r.ToList())
.ToList();
var numOfColumns = modifiedRows[0].Count;
var numOfRows = modifiedRows.Count;
@@ -97,9 +109,233 @@ public class Dish(
return modifiedRows;
}
public long CalculateTotalLoad()
private List<List<char>> TiltDishToWest(List<List<char>> rows)
{
var rows = TiltDishToNorth(Rows);
// create a copy of the rows which we can modify
var modifiedRows = rows
.Select(r => r.ToList())
.ToList();
var numOfRows = modifiedRows.Count;
var numOfColumns = modifiedRows[0].Count;
// iterate over the rows from top to bottom
for (var currentRowIndex = 0; currentRowIndex < numOfRows; currentRowIndex++)
{
// iterate over the columns from left to right
for (var currentColumnIndex = 0; currentColumnIndex < numOfRows; currentColumnIndex++)
{
// get the current symbol
var current = modifiedRows[currentRowIndex][currentColumnIndex];
// if the current symbol is an empty space
if (current is EmptySpaceSymbol)
{
// iterate over the columns to the left of the current column
for (var i = currentColumnIndex + 1; i < numOfColumns; i++)
{
// get the next symbol in the current row
var next = modifiedRows[currentRowIndex][i];
// if the next symbol is a square rock
// we can stop iterating over the columns to the left
// because the square rock blocks moving
// any round rock to fill the empty space
if (next is SquareRockSymbol)
{
break;
}
// if the next symbol is a round rock
// we can move the round rock to the empty space
// and stop iterating over the columns to the left
if (next is RoundRockSymbol)
{
modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol;
modifiedRows[currentRowIndex][i] = EmptySpaceSymbol;
break;
}
}
}
}
}
return modifiedRows;
}
private List<List<char>> TiltDishToSouth(List<List<char>> rows)
{
// create a copy of the rows which we can modify
var modifiedRows = rows
.Select(r => r.ToList())
.ToList();
var numOfColumns = modifiedRows[0].Count;
var numOfRows = modifiedRows.Count;
// iterate over the columns from left to right
for (var currentColumnIndex = 0; currentColumnIndex < numOfColumns; currentColumnIndex++)
{
// iterate over the rows from bottom to top
for (var currentRowIndex = numOfRows - 1; currentRowIndex >= 0; currentRowIndex--)
{
// get the current symbol
var current = modifiedRows[currentRowIndex][currentColumnIndex];
// if the current symbol is an empty space
if (current is EmptySpaceSymbol)
{
// iterate over the rows above the current row
// starting from the row before the current row
for (var i = currentRowIndex - 1; i >= 0; i--)
{
// get the next symbol in the current column
var next = modifiedRows[i][currentColumnIndex];
// if the next symbol is a square rock
// we can stop iterating over the rows below
// because the square rock blocks moving
// any round rock to fill the empty space
if (next is SquareRockSymbol)
{
break;
}
// if the next symbol is a round rock
// we can move the round rock to the empty space
// and stop iterating over the rows below
if (next is RoundRockSymbol)
{
modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol;
modifiedRows[i][currentColumnIndex] = EmptySpaceSymbol;
break;
}
}
}
}
}
return modifiedRows;
}
private List<List<char>> TiltDishToEast(List<List<char>> rows)
{
// create a copy of the rows which we can modify
var modifiedRows = rows
.Select(r => r.ToList())
.ToList();
var numOfRows = modifiedRows.Count;
var numOfColumns = modifiedRows[0].Count;
// iterate over the rows from top to bottom
for (var currentRowIndex = 0; currentRowIndex < numOfRows; currentRowIndex++)
{
// iterate over the columns from right to left
for (var currentColumnIndex = numOfColumns - 1; currentColumnIndex >= 0; currentColumnIndex--)
{
// get the current symbol
var current = modifiedRows[currentRowIndex][currentColumnIndex];
// if the current symbol is an empty space
if (current is EmptySpaceSymbol)
{
// iterate over the columns to the left of the current column
for (var i = currentColumnIndex - 1; i >= 0; i--)
{
// get the next symbol in the current row
var next = modifiedRows[currentRowIndex][i];
// if the next symbol is a square rock
// we can stop iterating over the columns to the left
// because the square rock blocks moving
// any round rock to fill the empty space
if (next is SquareRockSymbol)
{
break;
}
// if the next symbol is a round rock
// we can move the round rock to the empty space
// and stop iterating over the columns to the left
if (next is RoundRockSymbol)
{
modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol;
modifiedRows[currentRowIndex][i] = EmptySpaceSymbol;
break;
}
}
}
}
}
return modifiedRows;
}
/// <summary>
/// Tilt the dish to the north, west, south and east.
/// </summary>
/// <param name="numOfCycles">The number of cycles to tilt the dish.</param>
/// <returns>An instance of the <see cref="Dish"/> class.</returns>
public Dish TiltDish(long numOfCycles)
{
var modifiedRows = Rows
.Select(r => r.ToList())
.ToList();
// create a cache to store the modified rows
// and the index of the cycle in which they were modified
// this should allow us to detect cycles
var cache = new Dictionary<string, long>();
for (var i = 0; i < numOfCycles; i++)
{
modifiedRows = TiltDishToNorth(modifiedRows);
modifiedRows = TiltDishToWest(modifiedRows);
modifiedRows = TiltDishToSouth(modifiedRows);
modifiedRows = TiltDishToEast(modifiedRows);
// use the modified rows as a key for the cache
var key = string.Join(Environment.NewLine, modifiedRows.Select(r => string.Join(string.Empty, r)));
// if the cache already contains the key
if (cache.TryGetValue(key, out var matchingIndex))
{
// calculate the start index of the current cycle
// and identify the key for the start index value
var cycleLength = i - matchingIndex;
var remainingCycles = numOfCycles - i;
var cycleIndex = remainingCycles % cycleLength;
var cycleStartIndex = cycleIndex + matchingIndex - 1;
var startCycleKey = cache.First(c => c.Value == cycleStartIndex).Key;
// parse the key for the start index value
// and use it as the modified rows
modifiedRows = startCycleKey
.Split(Environment.NewLine)
.Select(s => s.ToList())
.ToList();
break;
}
// add the modified rows to the cache
cache.Add(key, i);
}
return new Dish(modifiedRows);
}
/// <summary>
/// Calculate the total load of the dish.
/// </summary>
/// <param name="spinDish">Whether to spin the dish.</param>
/// <returns>The total load of the dish.</returns>
public long CalculateTotalLoad(bool spinDish = false)
{
var rows = spinDish
? TiltDish(1_000_000_000).Rows
: TiltDishToNorth(Rows);
rows.Reverse();
return rows
@@ -111,6 +347,11 @@ public class Dish(
.Sum();
}
/// <summary>
/// Parse the input into an instance of the <see cref="Dish"/> class.
/// </summary>
/// <param name="input">The input to parse.</param>
/// <returns>An instance of the <see cref="Dish"/> class.</returns>
public static Dish Parse(string[] input)
{
var inputList = input
+27 -27
View File
@@ -42,30 +42,30 @@ dotnet build
## Challenges
| Day | Problem | Solution | Status | Notes |
| --- | -------------------------- | :---------------------------------: | :----: | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| 01 | [Problem](./01/PROBLEM.md) | [Solution](./01/Trebuchet/) | ✅ | The trickiest part here was accounting for overlapping digit words. |
| 02 | [Problem](./02/PROBLEM.md) | [Solution](./02/CubeConundrum/) | ✅ | The key to me here was to parse the input into a useful model. |
| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/GearRatios/) | ✅ | The edge case that got me here was lines ending with a part number. |
| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/Scratchcards/) | ✅ | Part 2 gets out of hand quickly with just 200 cards. |
| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/IYGASAF/) | ✅ | I brute forced part 2 using parallelism. I know shame. |
| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/WaitForIt/) | ✅ | Thank goodness part 2 was not like 5's part 2. 😅 |
| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/CamelCards/) | ✅ | What took me longest here was I missed a case when jokers are wild and there are three groups of cards. |
| 08 | [Problem](./08/PROBLEM.md) | [Solution](./08/HauntedWasteland/) | ✅ | Got to implement a least common multiple algorithm for part 2. |
| 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/MirageMaintenance/) | ✅ | Part 1 took me unnecessarily long. The bug was summing a line to check for all zeros is bad idea when negative numbers are involved. |
| 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/PipeMaze/) | ✅ | So...my solutions are rather slow, but they work. Part 2 appears to have a mathematical solution, but scanning is what I came up with on my own. |
| 11 | [Problem](./11/PROBLEM.md) | [Solution](./11/CosmicExpansion/) | ✅ | Expanding the universe was fine in part 1, but part 2 showed I actually needed to calculate the expansion instead of expanding the input. |
| 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/HotSprings/) | ✅ | This one stretched my skills. I needed lots of help from the interwebz. |
| 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/PointOfIncidence/) | ✅ | This solution is the one I think I'm most proud of so far. |
| 14 | [Problem](./14/PROBLEM.md) | [Solution](./14/) | ⌛ |
| 15 | [Problem](./15/PROBLEM.md) | [Solution](./15/) | ⌛ |
| 16 | [Problem](./16/PROBLEM.md) | [Solution](./16/) | ⌛ |
| 17 | [Problem](./17/PROBLEM.md) | [Solution](./17/) | ⌛ |
| 18 | [Problem](./18/PROBLEM.md) | [Solution](./18/) | ⌛ |
| 19 | [Problem](./19/PROBLEM.md) | [Solution](./19/) | ⌛ |
| 20 | [Problem](./20/PROBLEM.md) | [Solution](./20/) | ⌛ |
| 21 | [Problem](./21/PROBLEM.md) | [Solution](./21/) | ⌛ |
| 22 | [Problem](./22/PROBLEM.md) | [Solution](./22/) | ⌛ |
| 23 | [Problem](./23/PROBLEM.md) | [Solution](./23/) | ⌛ |
| 24 | [Problem](./24/PROBLEM.md) | [Solution](./24/) | ⌛ |
| 25 | [Problem](./25/PROBLEM.md) | [Solution](./25/) | ⌛ |
| Day | Problem | Solution | Status | Notes |
| --- | -------------------------- | :--------------------------------------: | :----: | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| 01 | [Problem](./01/PROBLEM.md) | [Solution](./01/Trebuchet/) | ✅ | The trickiest part here was accounting for overlapping digit words. |
| 02 | [Problem](./02/PROBLEM.md) | [Solution](./02/CubeConundrum/) | ✅ | The key to me here was to parse the input into a useful model. |
| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/GearRatios/) | ✅ | The edge case that got me here was lines ending with a part number. |
| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/Scratchcards/) | ✅ | Part 2 gets out of hand quickly with just 200 cards. |
| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/IYGASAF/) | ✅ | I brute forced part 2 using parallelism. I know shame. |
| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/WaitForIt/) | ✅ | Thank goodness part 2 was not like 5's part 2. 😅 |
| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/CamelCards/) | ✅ | What took me longest here was I missed a case when jokers are wild and there are three groups of cards. |
| 08 | [Problem](./08/PROBLEM.md) | [Solution](./08/HauntedWasteland/) | ✅ | Got to implement a least common multiple algorithm for part 2. |
| 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/MirageMaintenance/) | ✅ | Part 1 took me unnecessarily long. The bug was summing a line to check for all zeros is bad idea when negative numbers are involved. |
| 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/PipeMaze/) | ✅ | So...my solutions are rather slow, but they work. Part 2 appears to have a mathematical solution, but scanning is what I came up with on my own. |
| 11 | [Problem](./11/PROBLEM.md) | [Solution](./11/CosmicExpansion/) | ✅ | Expanding the universe was fine in part 1, but part 2 showed I actually needed to calculate the expansion instead of expanding the input. |
| 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/HotSprings/) | ✅ | This one stretched my skills. I needed lots of help from the interwebz. |
| 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/PointOfIncidence/) | ✅ | This solution is the one I think I'm most proud of so far. |
| 14 | [Problem](./14/PROBLEM.md) | [Solution](./14/ParabolicReflectorDish/) | | Cycles and a cache...that's all I'm going to say. |
| 15 | [Problem](./15/PROBLEM.md) | [Solution](./15/) | ⌛ |
| 16 | [Problem](./16/PROBLEM.md) | [Solution](./16/) | ⌛ |
| 17 | [Problem](./17/PROBLEM.md) | [Solution](./17/) | ⌛ |
| 18 | [Problem](./18/PROBLEM.md) | [Solution](./18/) | ⌛ |
| 19 | [Problem](./19/PROBLEM.md) | [Solution](./19/) | ⌛ |
| 20 | [Problem](./20/PROBLEM.md) | [Solution](./20/) | ⌛ |
| 21 | [Problem](./21/PROBLEM.md) | [Solution](./21/) | ⌛ |
| 22 | [Problem](./22/PROBLEM.md) | [Solution](./22/) | ⌛ |
| 23 | [Problem](./23/PROBLEM.md) | [Solution](./23/) | ⌛ |
| 24 | [Problem](./24/PROBLEM.md) | [Solution](./24/) | ⌛ |
| 25 | [Problem](./25/PROBLEM.md) | [Solution](./25/) | ⌛ |