From 4621e17a8eb3e3a8faef87bc3c04b0764706f08a Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 6 Dec 2023 23:23:47 -0600 Subject: [PATCH 1/4] chore: add stopwatch --- 06/WaitForIt/Program.cs | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/06/WaitForIt/Program.cs b/06/WaitForIt/Program.cs index e7fcf86..f422420 100644 --- a/06/WaitForIt/Program.cs +++ b/06/WaitForIt/Program.cs @@ -1,4 +1,6 @@ -namespace WaitForIt; +using System.Diagnostics; + +namespace WaitForIt; public class Program { @@ -20,6 +22,9 @@ public class Program var input = await File.ReadAllLinesAsync(args[0]); var isPart2 = args.Length > 1 && args[1] == "part2"; + var stopWatch = new Stopwatch(); + stopWatch.Start(); + long result = isPart2 ? parser .ParseRace(input) @@ -28,13 +33,15 @@ public class Program .Select(r => r.CalculateNumberOfWaysToWin()) .Aggregate((long)1, (acc, curr) => acc * curr); + stopWatch.Stop(); + if (isPart2) { - Console.WriteLine($"The number of ways to win is {result}."); + Console.WriteLine($"The number of ways to win is {result}. ({stopWatch.ElapsedMilliseconds}ms)"); } else { - Console.WriteLine($"The total number of ways to win is {result}."); + Console.WriteLine($"The total number of ways to win is {result}. ({stopWatch.ElapsedMilliseconds}ms)"); } return (int)result; @@ -117,19 +124,29 @@ public class Race( /// The number of ways the race can be won. public long CalculateNumberOfWaysToWin() { - var numberOfWaysToWin = 0; + var minDuration = 0.0; + var maxDuration = Math.Floor(Duration / 2.0); - for (var secsHeld = 0; secsHeld < Duration; secsHeld++) + while (minDuration < maxDuration - 1) { - var speed = 1 * secsHeld; - var distance = speed * (Duration - secsHeld); + var middleDuration = Math.Floor((maxDuration + minDuration) / 2); + var speed = 1 * middleDuration; + var distance = speed * (Duration - middleDuration); - if (distance > DistanceRecord) + if (distance >= DistanceRecord) { - numberOfWaysToWin++; + maxDuration = middleDuration; + } + else + { + minDuration = middleDuration; } } - return numberOfWaysToWin; + var result = Duration % 2 == 0 + ? Duration - ((long)maxDuration * 2) - 1 + : Duration - ((long)maxDuration * 2) + 1; + + return result; } } \ No newline at end of file From a84b0d07f063fc2e013c3c71af196d40a526c82b Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:06:26 -0600 Subject: [PATCH 2/4] feat: solve part 1 --- 07/CamelCards.Tests/CamelCards.Tests.csproj | 36 ++++ 07/CamelCards.Tests/CardTests.cs | 31 +++ 07/CamelCards.Tests/GlobalUsings.cs | 2 + 07/CamelCards.Tests/HandTests.cs | 204 ++++++++++++++++++++ 07/CamelCards.Tests/ProgramTests.cs | 11 ++ 07/CamelCards.Tests/TurnTests.cs | 95 +++++++++ 07/CamelCards/CamelCards.csproj | 10 + 07/CamelCards/Program.cs | 173 +++++++++++++++++ AdventOfCode2023.sln | 16 ++ 9 files changed, 578 insertions(+) create mode 100644 07/CamelCards.Tests/CamelCards.Tests.csproj create mode 100644 07/CamelCards.Tests/CardTests.cs create mode 100644 07/CamelCards.Tests/GlobalUsings.cs create mode 100644 07/CamelCards.Tests/HandTests.cs create mode 100644 07/CamelCards.Tests/ProgramTests.cs create mode 100644 07/CamelCards.Tests/TurnTests.cs create mode 100644 07/CamelCards/CamelCards.csproj create mode 100644 07/CamelCards/Program.cs diff --git a/07/CamelCards.Tests/CamelCards.Tests.csproj b/07/CamelCards.Tests/CamelCards.Tests.csproj new file mode 100644 index 0000000..9005157 --- /dev/null +++ b/07/CamelCards.Tests/CamelCards.Tests.csproj @@ -0,0 +1,36 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + PreserveNewest + + + + diff --git a/07/CamelCards.Tests/CardTests.cs b/07/CamelCards.Tests/CardTests.cs new file mode 100644 index 0000000..28bb3d0 --- /dev/null +++ b/07/CamelCards.Tests/CardTests.cs @@ -0,0 +1,31 @@ +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)] + public void Card_WhenGivenValidCharacter_ItShouldReturnCardWithExpectedStrength(char character, int expectedStrength) + { + var card = new Card(character); + card.Strength.Should().Be(expectedStrength); + } + + [Fact] + public void Card_WhenGivenInvalidCardCharacter_ItShouldThrowArgumentException() + { + Action act = () => new Card('X'); + act.Should().Throw(); + } +} \ No newline at end of file diff --git a/07/CamelCards.Tests/GlobalUsings.cs b/07/CamelCards.Tests/GlobalUsings.cs new file mode 100644 index 0000000..7fef4b0 --- /dev/null +++ b/07/CamelCards.Tests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions; \ No newline at end of file diff --git a/07/CamelCards.Tests/HandTests.cs b/07/CamelCards.Tests/HandTests.cs new file mode 100644 index 0000000..6f75abc --- /dev/null +++ b/07/CamelCards.Tests/HandTests.cs @@ -0,0 +1,204 @@ +namespace CamelCards.Tests; + +public class HandTests +{ + [Theory] + [MemberData(nameof(TestData.HandTypeTestData), MemberType = typeof(TestData))] + public void Type_WhenGivenListOfCards_ItShouldReturnExpectedHandType(List cards, HandType expectedHandType) + { + var hand = new Hand(cards); + hand.Type.Should().Be(expectedHandType); + } + + [Theory] + [MemberData(nameof(TestData.CompareToTestData), MemberType = typeof(TestData))] + public void CompareTo_WhenGivenHandWithHigherType_ItShouldReturnNegative1(Hand hand, Hand otherHand, int sortResult) + { + hand.CompareTo(otherHand).Should().Be(sortResult); + } + + public static class TestData + { + public static IEnumerable CompareToTestData => + new List + { + new object[] + { + new Hand( + [ + new('A'), + new('A'), + new('A'), + new('A'), + new('A'), + ] + ), + new Hand( + [ + new('A'), + new('A'), + new('8'), + new('A'), + new('A'), + ] + ), + 1, + }, + new object[] + { + new Hand( + [ + new('A'), + new('A'), + new('8'), + new('A'), + new('A'), + ] + ), + new Hand( + [ + new('A'), + new('A'), + new('A'), + new('A'), + new('A'), + ] + ), + -1, + }, + new object[] + { + new Hand( + [ + new('A'), + new('A'), + new('A'), + new('A'), + new('A'), + ] + ), + new Hand( + [ + new('A'), + new('A'), + new('A'), + new('A'), + new('A'), + ] + ), + 0, + }, + new object[] + { + new Hand( + [ + new('3'), + new('3'), + new('3'), + new('3'), + new('2'), + ] + ), + new Hand( + [ + new('2'), + new('A'), + new('A'), + new('A'), + new('A'), + ] + ), + 1, + } + }; + + public static IEnumerable HandTypeTestData => + new List + { + new object[] + { + new List + { + new('A'), + new('A'), + new('A'), + new('A'), + new('A'), + }, + HandType.FiveOfAKind, + }, + new object[] + { + new List + { + new('A'), + new('A'), + new('8'), + new('A'), + new('A'), + }, + HandType.FourOfAKind, + }, + new object[] + { + new List + { + new('2'), + new('3'), + new('3'), + new('3'), + new('2'), + }, + HandType.FullHouse, + }, + new object[] + { + new List + { + new('T'), + new('T'), + new('T'), + new('9'), + new('8'), + }, + HandType.ThreeOfAKind, + }, + new object[] + { + new List + { + new('2'), + new('3'), + new('4'), + new('3'), + new('2'), + }, + HandType.TwoPair, + }, + new object[] + { + new List + { + new('A'), + new('2'), + new('3'), + new('A'), + new('4'), + }, + HandType.OnePair, + }, + new object[] + { + new List + { + new('2'), + new('3'), + new('4'), + new('5'), + new('6'), + }, + HandType.HighCard, + }, + }; + } +} \ No newline at end of file diff --git a/07/CamelCards.Tests/ProgramTests.cs b/07/CamelCards.Tests/ProgramTests.cs new file mode 100644 index 0000000..6e0791a --- /dev/null +++ b/07/CamelCards.Tests/ProgramTests.cs @@ -0,0 +1,11 @@ +namespace CamelCards.Tests; + +public class ProgramTests +{ + [Fact] + public async Task Main_WhenCalledWithInputFile_ItShouldReturnExpectedResult() + { + var result = await Program.Main(["INPUT.txt"]); + result.Should().Be(251287184); + } +} \ No newline at end of file diff --git a/07/CamelCards.Tests/TurnTests.cs b/07/CamelCards.Tests/TurnTests.cs new file mode 100644 index 0000000..58e2f51 --- /dev/null +++ b/07/CamelCards.Tests/TurnTests.cs @@ -0,0 +1,95 @@ +namespace CamelCards.Tests; + +public class TurnTests +{ + [Fact] + public void Parse_WhenGivenTurnInput_ItShouldReturnExpectedTurnInstance() + { + var turn = Turn.Parse("32T3K 765"); + turn.Hand.Cards.Should().BeEquivalentTo(new List + { + new('3'), + new('2'), + new('T'), + new('3'), + new('K'), + }); + + turn.Bid.Should().Be(765); + } + + [Fact] + public void ToString_WhenCalled_ItShouldReturnStringRepresentationOfTurn() + { + var turn = "32T3K 765"; + Turn.Parse(turn).ToString().Should().Be(turn); + } + + [Fact] + public void OrderBy_WhenGivenListOfTurns_ItShouldBeAbleToOrderThemAccordingToHandStrength() + { + var turns = TestData.TestTurns + .Select(Turn.Parse) + .ToList(); + + turns + .OrderBy(t => t.Hand) + .Select(t => t.ToString()) + .Should() + .BeEquivalentTo( + [ + "32T3K 765", + "KTJJT 220", + "KK677 28", + "T55J5 684", + "QQQJA 483", + ] + ); + } + + [Fact] + public void OrderByDescending_WhenGivenListOfTurns_ItShouldBeAbleToOrderThemAccordingToHandStrength() + { + var turns = TestData.TestTurns + .Select(Turn.Parse) + .ToList(); + + turns + .OrderByDescending(t => t.Hand) + .Select(t => t.ToString()) + .Should() + .BeEquivalentTo( + [ + "QQQJA 483", + "T55J5 684", + "KK677 28", + "KTJJT 220", + "32T3K 765", + ] + ); + } + + [Fact] + public void Turn_GivenListOfTurns_ItShouldBeAbleToCalculateTotalWinnings() + { + TestData.TestTurns + .Select(Turn.Parse) + .OrderBy(t => t.Hand) + .Select((turn, index) => turn.Bid * (index + 1)) + .Sum() + .Should() + .Be(6440); + } + + public static class TestData + { + public static readonly string[] TestTurns = + [ + "32T3K 765", + "T55J5 684", + "KK677 28", + "KTJJT 220", + "QQQJA 483" + ]; + } +} \ No newline at end of file diff --git a/07/CamelCards/CamelCards.csproj b/07/CamelCards/CamelCards.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/07/CamelCards/CamelCards.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/07/CamelCards/Program.cs b/07/CamelCards/Program.cs new file mode 100644 index 0000000..30ce0e2 --- /dev/null +++ b/07/CamelCards/Program.cs @@ -0,0 +1,173 @@ +using System.Diagnostics; + +namespace CamelCards; + + +public class Program +{ + public static async Task 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 input = await File.ReadAllLinesAsync(args[0]); + + var stopWatch = new Stopwatch(); + stopWatch.Start(); + + var result = input + .Select(Turn.Parse) + .OrderBy(t => t.Hand) + .Select((turn, index) => turn.Bid * (index + 1)) + .Sum(); + + stopWatch.Stop(); + + Console.WriteLine($"The total winnings is {result}. ({stopWatch.ElapsedMilliseconds}ms)"); + + return result; + } +} + +public class Turn( + Hand hand, + int bid +) +{ + public Hand Hand { get; init; } = hand; + public int Bid { get; init; } = bid; + + public override string ToString() + { + return $"{string.Join("", Hand.Cards.Select(c => c.Value))} {Bid}"; + } + + public static Turn Parse(string turnInput) + { + var parts = turnInput.Split(' ', StringSplitOptions.TrimEntries); + var cards = parts[0].Select(c => new Card(c)).ToList(); + var bid = int.Parse(parts[1]); + + return new Turn(new(cards), bid); + } +} + +public class Hand : IComparable +{ + public List Cards { get; init; } + + public Hand(List cards) + { + if (cards.Count != 5) + { + throw new ArgumentException("A hand must have 5 cards"); + } + + 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 int CompareTo(Hand? other) + { + int result; + + if (other == null) + { + return 1; + } + + result = Type.CompareTo(other.Type); + + if (result != 0) + { + return result; + } + + for (int i = 0; i < Cards.Count; i++) + { + var currentCard = Cards[i]; + var otherCard = other.Cards[i]; + + var cardComparison = currentCard.Strength.CompareTo(otherCard.Strength); + + if (i == Cards.Count - 1) + { + result = cardComparison; + } + + if (cardComparison != 0) + { + result = cardComparison; + break; + } + } + + return result; + } +} + +public enum HandType +{ + HighCard, + OnePair, + TwoPair, + ThreeOfAKind, + FullHouse, + FourOfAKind, + FiveOfAKind, +} + + +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, + }; + + public char Value { get; init; } + public int Strength { get; init; } + + public Card(char value) + { + var isValidCardChar = Cards.TryGetValue(value, out var strength); + + if (!isValidCardChar) + { + throw new ArgumentException($"Invalid card value: {value}"); + } + + Value = value; + Strength = strength; + } +} \ No newline at end of file diff --git a/AdventOfCode2023.sln b/AdventOfCode2023.sln index 8590e39..e11e266 100644 --- a/AdventOfCode2023.sln +++ b/AdventOfCode2023.sln @@ -39,6 +39,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaitForIt", "06\WaitForIt\W EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaitForIt.Tests", "06\WaitForIt.Tests\WaitForIt.Tests.csproj", "{8C89E1D0-2617-4B89-BA9C-9189FC69BC43}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "07", "07", "{7F8AD027-D8D7-407B-9E08-B363B7B34621}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CamelCards", "07\CamelCards\CamelCards.csproj", "{DC16CBE1-B1FA-4E6B-AAD8-43CDECA46659}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CamelCards.Tests", "07\CamelCards.Tests\CamelCards.Tests.csproj", "{8F04A78E-D2E1-41AC-B43C-74608B7B4FCB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -96,6 +102,14 @@ Global {8C89E1D0-2617-4B89-BA9C-9189FC69BC43}.Debug|Any CPU.Build.0 = Debug|Any CPU {8C89E1D0-2617-4B89-BA9C-9189FC69BC43}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C89E1D0-2617-4B89-BA9C-9189FC69BC43}.Release|Any CPU.Build.0 = Release|Any CPU + {DC16CBE1-B1FA-4E6B-AAD8-43CDECA46659}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC16CBE1-B1FA-4E6B-AAD8-43CDECA46659}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC16CBE1-B1FA-4E6B-AAD8-43CDECA46659}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC16CBE1-B1FA-4E6B-AAD8-43CDECA46659}.Release|Any CPU.Build.0 = Release|Any CPU + {8F04A78E-D2E1-41AC-B43C-74608B7B4FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F04A78E-D2E1-41AC-B43C-74608B7B4FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F04A78E-D2E1-41AC-B43C-74608B7B4FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F04A78E-D2E1-41AC-B43C-74608B7B4FCB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {3F8EAC09-4BC7-43AA-B72B-48DDD2710F6D} = {8C29858C-623A-461A-BF0B-254E151CD9C2} @@ -110,5 +124,7 @@ Global {CB90004D-9420-4411-9110-B50274537FE5} = {5067FD6B-8F03-4502-AB83-8A391D801B47} {B3D736D4-EFEE-44AA-B199-1A1C803F1793} = {86704890-D6B8-4166-853B-F1424B8396C7} {8C89E1D0-2617-4B89-BA9C-9189FC69BC43} = {86704890-D6B8-4166-853B-F1424B8396C7} + {DC16CBE1-B1FA-4E6B-AAD8-43CDECA46659} = {7F8AD027-D8D7-407B-9E08-B363B7B34621} + {8F04A78E-D2E1-41AC-B43C-74608B7B4FCB} = {7F8AD027-D8D7-407B-9E08-B363B7B34621} EndGlobalSection EndGlobal 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 3/4] 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/) | ⌛ | From 9dbc35f028f48bed42ba2d6276e82eb80ebc4933 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Thu, 7 Dec 2023 19:35:40 -0600 Subject: [PATCH 4/4] chore: dotnet format --- 06/WaitForIt.Tests/GlobalUsings.cs | 2 +- 07/CamelCards.Tests/GlobalUsings.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/06/WaitForIt.Tests/GlobalUsings.cs b/06/WaitForIt.Tests/GlobalUsings.cs index 2d5b473..d6f0167 100644 --- a/06/WaitForIt.Tests/GlobalUsings.cs +++ b/06/WaitForIt.Tests/GlobalUsings.cs @@ -1,3 +1,3 @@ global using FluentAssertions; -global using Xunit; +global using Xunit; \ No newline at end of file diff --git a/07/CamelCards.Tests/GlobalUsings.cs b/07/CamelCards.Tests/GlobalUsings.cs index 7fef4b0..2d5b473 100644 --- a/07/CamelCards.Tests/GlobalUsings.cs +++ b/07/CamelCards.Tests/GlobalUsings.cs @@ -1,2 +1,3 @@ +global using FluentAssertions; + global using Xunit; -global using FluentAssertions; \ No newline at end of file