From 887e139d970d43236d4182fc5d8b45d22325d7d6 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Thu, 7 Dec 2023 19:34:55 -0600 Subject: [PATCH] feat: solve part 2 --- 07/CamelCards.Tests/CardTests.cs | 27 +++---- 07/CamelCards.Tests/ProgramTests.cs | 7 ++ 07/CamelCards.Tests/TurnTests.cs | 109 +++++++++++++++++++++++++++- 07/CamelCards/Program.cs | 82 ++++++++++++++------- README.md | 18 ++--- 5 files changed, 192 insertions(+), 51 deletions(-) diff --git a/07/CamelCards.Tests/CardTests.cs b/07/CamelCards.Tests/CardTests.cs index 28bb3d0..6a9daaf 100644 --- a/07/CamelCards.Tests/CardTests.cs +++ b/07/CamelCards.Tests/CardTests.cs @@ -3,19 +3,20 @@ namespace CamelCards.Tests; public class CardTests { [Theory] - [InlineData('A', 12)] - [InlineData('K', 11)] - [InlineData('Q', 10)] - [InlineData('J', 9)] - [InlineData('T', 8)] - [InlineData('9', 7)] - [InlineData('8', 6)] - [InlineData('7', 5)] - [InlineData('6', 4)] - [InlineData('5', 3)] - [InlineData('4', 2)] - [InlineData('3', 1)] - [InlineData('2', 0)] + [InlineData('A', 13)] + [InlineData('K', 12)] + [InlineData('Q', 11)] + [InlineData('J', 10)] + [InlineData('T', 9)] + [InlineData('9', 8)] + [InlineData('8', 7)] + [InlineData('7', 6)] + [InlineData('6', 5)] + [InlineData('5', 4)] + [InlineData('4', 3)] + [InlineData('3', 2)] + [InlineData('2', 1)] + [InlineData('W', 0)] public void Card_WhenGivenValidCharacter_ItShouldReturnCardWithExpectedStrength(char character, int expectedStrength) { var card = new Card(character); diff --git a/07/CamelCards.Tests/ProgramTests.cs b/07/CamelCards.Tests/ProgramTests.cs index 6e0791a..642e791 100644 --- a/07/CamelCards.Tests/ProgramTests.cs +++ b/07/CamelCards.Tests/ProgramTests.cs @@ -8,4 +8,11 @@ public class ProgramTests var result = await Program.Main(["INPUT.txt"]); result.Should().Be(251287184); } + + [Fact] + public async Task Main_WhenCalledWithInputFileAsPart2_ItShouldReturnExpectedResult() + { + var result = await Program.Main(["INPUT.txt", "part2"]); + result.Should().Be(250757288); + } } \ No newline at end of file diff --git a/07/CamelCards.Tests/TurnTests.cs b/07/CamelCards.Tests/TurnTests.cs index 58e2f51..c3cfd50 100644 --- a/07/CamelCards.Tests/TurnTests.cs +++ b/07/CamelCards.Tests/TurnTests.cs @@ -29,7 +29,7 @@ public class TurnTests public void OrderBy_WhenGivenListOfTurns_ItShouldBeAbleToOrderThemAccordingToHandStrength() { var turns = TestData.TestTurns - .Select(Turn.Parse) + .Select(t => Turn.Parse(t)) .ToList(); turns @@ -51,7 +51,7 @@ public class TurnTests public void OrderByDescending_WhenGivenListOfTurns_ItShouldBeAbleToOrderThemAccordingToHandStrength() { var turns = TestData.TestTurns - .Select(Turn.Parse) + .Select(t => Turn.Parse(t)) .ToList(); turns @@ -69,11 +69,33 @@ public class TurnTests ); } + [Fact] + public void OrderBy_WhenGivenListOfTurnsAndJokersAreTreatedAsWild_ItShouldBeAbleToOrderThemAccordingToHandStrength() + { + var turns = TestData.TestTurns + .Select(t => Turn.Parse(t, true)) + .ToList(); + + turns + .OrderBy(t => t.Hand) + .Select(t => t.ToString()) + .Should() + .BeEquivalentTo( + [ + "32T3K 765", + "KK677 28", + "T55J5 684", + "QQQJA 483", + "KTJJT 220", + ] + ); + } + [Fact] public void Turn_GivenListOfTurns_ItShouldBeAbleToCalculateTotalWinnings() { TestData.TestTurns - .Select(Turn.Parse) + .Select(t => Turn.Parse(t)) .OrderBy(t => t.Hand) .Select((turn, index) => turn.Bid * (index + 1)) .Sum() @@ -81,8 +103,89 @@ public class TurnTests .Be(6440); } + [Fact] + public void Turn_GivenListOfTurnsAndJokersAreWild_ItShouldBeAbleToCalculateTotalWinnings() + { + TestData.TestTurns + .Select(t => Turn.Parse(t, true)) + .OrderBy(t => t.Hand) + .Select((turn, index) => turn.Bid * (index + 1)) + .Sum() + .Should() + .Be(5905); + } + + [Theory] + [MemberData(nameof(TestData.TestTurnsWithJokersData), MemberType = typeof(TestData))] + public void Turn_GivenListOfTurnsWithJokers_ItShouldHaveCorrectHandType(string turn, HandType expectedHandType) + { + Turn.Parse(turn, true).Hand.Type.Should().Be(expectedHandType); + } + public static class TestData { + public static readonly string[] InputTurnsWithJokers = File.ReadAllLines("INPUT.txt").Where(l => l.Contains('J')).ToArray(); + + public static IEnumerable TestTurnsWithJokersData => + new List + { + new object[] + { + "4446J 425", + HandType.FourOfAKind, + }, + new object[] + { + "26J93 60", + HandType.OnePair, + }, + new object[] + { + "TQ9JQ 554", + HandType.ThreeOfAKind, + }, + new object[] + { + "J373A 525", + HandType.ThreeOfAKind, + }, + new object[] + { + "44JJ4 738", + HandType.FiveOfAKind, + }, + new object[] + { + "JTK95 684", + HandType.OnePair, + }, + new object[] + { + "5J39Q 743", + HandType.OnePair, + }, + new object[] + { + "222J2 833", + HandType.FiveOfAKind, + }, + new object[] + { + "JJJ44 668", + HandType.FiveOfAKind, + }, + new object[] + { + "4JK47 317", + HandType.ThreeOfAKind, + }, + new object[] + { + "66J4J 253", + HandType.FourOfAKind, + } + }; + public static readonly string[] TestTurns = [ "32T3K 765", diff --git a/07/CamelCards/Program.cs b/07/CamelCards/Program.cs index 30ce0e2..acfed49 100644 --- a/07/CamelCards/Program.cs +++ b/07/CamelCards/Program.cs @@ -19,13 +19,14 @@ public class Program return -2; } + var jokersWild = args.Length > 1 && args[1] == "part2"; var input = await File.ReadAllLinesAsync(args[0]); var stopWatch = new Stopwatch(); stopWatch.Start(); var result = input - .Select(Turn.Parse) + .Select(line => Turn.Parse(line, jokersWild)) .OrderBy(t => t.Hand) .Select((turn, index) => turn.Bid * (index + 1)) .Sum(); @@ -48,13 +49,23 @@ public class Turn( public override string ToString() { - return $"{string.Join("", Hand.Cards.Select(c => c.Value))} {Bid}"; + var cards = Hand.Cards + .Select(c => + { + return c.Value == 'W' + ? 'J' + : c.Value; + }); + + return $"{string.Join("", cards)} {Bid}"; } - public static Turn Parse(string turnInput) + public static Turn Parse(string turnInput, bool jokersWild = false) { var parts = turnInput.Split(' ', StringSplitOptions.TrimEntries); - var cards = parts[0].Select(c => new Card(c)).ToList(); + var cards = parts[0] + .Select(c => jokersWild && c == 'J' ? new Card('W') : new Card(c)) + .ToList(); var bid = int.Parse(parts[1]); return new Turn(new(cards), bid); @@ -75,15 +86,33 @@ public class Hand : IComparable Cards = cards; } - public HandType Type => Cards.GroupBy(c => c.Value).Count() switch - { - 5 => HandType.HighCard, - 4 => HandType.OnePair, - 3 => Cards.GroupBy(c => c.Value).Any(g => g.Count() == 3) ? HandType.ThreeOfAKind : HandType.TwoPair, - 2 => Cards.GroupBy(c => c.Value).Any(g => g.Count() == 4) ? HandType.FourOfAKind : HandType.FullHouse, - 1 => HandType.FiveOfAKind, - _ => throw new ApplicationException("Hand has no defined type"), - }; + public HandType Type => Cards.Any(c => c.Value == 'W') + ? Cards + .GroupBy(c => c.Value) + .Count() switch + { + 5 => HandType.OnePair, + 4 => HandType.ThreeOfAKind, + 3 => Cards.GroupBy(c => c.Value).Any(g => g.Count() == 3) + ? HandType.FourOfAKind + : Cards.Count(c => c.Value == 'W') == 2 + ? HandType.FourOfAKind + : HandType.FullHouse, + 2 or + 1 => HandType.FiveOfAKind, + _ => throw new ApplicationException("Hand has no defined type"), + } + : Cards + .GroupBy(c => c.Value) + .Count() switch + { + 5 => HandType.HighCard, + 4 => HandType.OnePair, + 3 => Cards.GroupBy(c => c.Value).Any(g => g.Count() == 3) ? HandType.ThreeOfAKind : HandType.TwoPair, + 2 => Cards.GroupBy(c => c.Value).Any(g => g.Count() == 4) ? HandType.FourOfAKind : HandType.FullHouse, + 1 => HandType.FiveOfAKind, + _ => throw new ApplicationException("Hand has no defined type"), + }; public int CompareTo(Hand? other) { @@ -140,19 +169,20 @@ public class Card { private static readonly Dictionary Cards = new() { - ['A'] = 12, - ['K'] = 11, - ['Q'] = 10, - ['J'] = 9, - ['T'] = 8, - ['9'] = 7, - ['8'] = 6, - ['7'] = 5, - ['6'] = 4, - ['5'] = 3, - ['4'] = 2, - ['3'] = 1, - ['2'] = 0, + ['A'] = 13, + ['K'] = 12, + ['Q'] = 11, + ['J'] = 10, + ['T'] = 9, + ['9'] = 8, + ['8'] = 7, + ['7'] = 6, + ['6'] = 5, + ['5'] = 4, + ['4'] = 3, + ['3'] = 2, + ['2'] = 1, + ['W'] = 0, }; public char Value { get; init; } diff --git a/README.md b/README.md index 90454b5..2deff4e 100644 --- a/README.md +++ b/README.md @@ -42,15 +42,15 @@ 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/) | ⌛ | +| 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/) | ⌛ | | 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/) | ⌛ | | 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/) | ⌛ |