diff --git a/04/Scratchcards.Tests/PuzzleSolverTests.cs b/04/Scratchcards.Tests/PuzzleSolverTests.cs index 2a14baa..b243570 100644 --- a/04/Scratchcards.Tests/PuzzleSolverTests.cs +++ b/04/Scratchcards.Tests/PuzzleSolverTests.cs @@ -12,24 +12,49 @@ public class PuzzleSolverTests result.Should().Be(expected); } + [Theory] + [MemberData(nameof(TestData.GetTotalCardCountData), MemberType = typeof(TestData))] + public void GetTotalCardCount_GivenCards_ItShouldReturnExpectedValue(string[] cards, int expected) + { + var result = _sut.GetTotalCardCount(cards); + result.Should().Be(expected); + } + public static class TestData { + private static readonly string[] ExampleInput = + [ + "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53", + "Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19", + "Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1", + "Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83", + "Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36", + "Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11", + ]; + private static string[] Input => File.ReadAllLines("INPUT.txt"); + public static IEnumerable GetTotalCardCountData => + new List + { + new object[] + { + ExampleInput, + 30, + }, + new object[] + { + Input, + 5920640, + }, + }; + public static IEnumerable SumCardValuesData => new List { new object[] { - new[] - { - "Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53", - "Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19", - "Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1", - "Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83", - "Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36", - "Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11", - }, + ExampleInput, 13, }, new object[] diff --git a/04/Scratchcards/Program.cs b/04/Scratchcards/Program.cs index e0acc3b..c838a87 100644 --- a/04/Scratchcards/Program.cs +++ b/04/Scratchcards/Program.cs @@ -19,40 +19,116 @@ public class Program var puzzleSolver = new PuzzleSolver(); var input = await File.ReadAllLinesAsync(args[0]); - var result = puzzleSolver.SumCardValues(input); + var result = args.Length is 2 && args[1] == "part2" + ? puzzleSolver.GetTotalCardCount(input) + : puzzleSolver.SumCardValues(input); - Console.WriteLine($"The sum of all card values is {result}."); + if (args.Length is 2 && args[1] == "part2") + { + Console.WriteLine($"The total number of cards is {result}."); + } + else + { + Console.WriteLine($"The total value of the cards is {result}."); + } return result; } } +/// +/// Solves the puzzle. +/// public class PuzzleSolver { + /// + /// Sums the value of the cards. + /// + /// The cards. + /// The sum of the cards. public int SumCardValues(string[] cards) => cards .Select(Card.Parse) .Sum(c => c.GetCardValue()); + + /// + /// Gets the total number of cards. + /// + /// The cards. + /// The total number of cards. + public int GetTotalCardCount(string[] cards) + { + var cardStringsToProcess = new List(cards); + + for (var i = 0; i < cardStringsToProcess.Count; i++) + { + var currentCardString = cardStringsToProcess[i]; + var currentCardStringIndex = Array.IndexOf(cards, currentCardString); + var currentCard = Card.Parse(currentCardString); + var numOfMatchingNumbersForCurrentCard = currentCard.MatchingNumbers.Count; + var cardIndexesToCopy = Enumerable.Range( + currentCardStringIndex + 1, + Math.Min( + numOfMatchingNumbersForCurrentCard, + cards.Length - currentCardStringIndex - 1 + ) + ); + + foreach (var index in cardIndexesToCopy) + { + cardStringsToProcess.Add(cards[index]); + } + } + + return cardStringsToProcess.Count; + } } +/// +/// Represents a card. +/// +/// The identifier. +/// The winning numbers. +/// The numbers. +/// An instance of . public class Card( int id, List winningNumbers, List numbers ) { + /// + /// Gets the identifier. + /// public int Id { get; init; } = id; + + /// + /// Gets the winning numbers. + /// public List WinningNumbers { get; init; } = winningNumbers; + + /// + /// Gets the numbers. + /// public List Numbers { get; init; } = numbers; - public int GetCardValue() - { - var commonNumbers = WinningNumbers.Intersect(Numbers).ToList(); + /// + /// Gets the matching numbers. + /// + public List MatchingNumbers => Numbers.Intersect(WinningNumbers).ToList(); - return commonNumbers.Count is 1 - ? 1 - : (int)Math.Pow(2, commonNumbers.Count - 1); - } + /// + /// Gets the value of the card. + /// + /// The value of the card. + public int GetCardValue() => MatchingNumbers.Count is 1 + ? 1 + : (int)Math.Pow(2, MatchingNumbers.Count - 1); + /// + /// Parses the specified input. + /// + /// The input. + /// An instance of . public static Card Parse(string input) { var parts = input.Split(':'); diff --git a/README.md b/README.md index c62875b..ce7b631 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ dotnet run -- part2 | 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/) | ⌛ | +| 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/) | ⌛ | | 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/) | ⌛ | | 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/) | ⌛ |