Skip to content

Commit

Permalink
Refactoring d07
Browse files Browse the repository at this point in the history
  • Loading branch information
derailed-dash committed Jan 1, 2024
1 parent 0c62c64 commit 4a5880d
Showing 1 changed file with 56 additions and 80 deletions.
136 changes: 56 additions & 80 deletions src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2426,7 +2426,7 @@
"\\end{align}\n",
"$$\n",
"\n",
"So we can use this to obtain the two roots, i.e. the hold times where where our distance is equal to the record distance. We're given distance $d$ and one duration $t$ in our input. (And we know $a$ is `1`.) Using the sample data we were given:\n",
"So we can use this to obtain the two roots, i.e. the hold times where our distance is equal to the record distance. We're given distance $d$ and one duration $t$ in our input. (And we know $a$ is `1`.) Using the sample data we were given:\n",
"\n",
"$$\n",
"h^2 - 71530h + 940200 = 0 \\\\\n",
Expand Down Expand Up @@ -2549,9 +2549,9 @@
" logger.debug(f\"{race_duration=}, {distance=}\")\n",
" \n",
" # solve using quadratic \n",
" discriminant = (-race_duration)**2 - (4 * distance)\n",
" h1 = int((-race_duration + math.sqrt(discriminant)) / 2)\n",
" h2 = int((-race_duration - math.sqrt(discriminant)) / 2)\n",
" discriminant = (-race_duration)**2 - (4 * distance) # bottom of our curve\n",
" h1 = int((-race_duration + math.sqrt(discriminant)) / 2) # max value\n",
" h2 = int((-race_duration - math.sqrt(discriminant)) / 2) # min value\n",
" \n",
" return abs(h1-h2)\n",
" \n",
Expand Down Expand Up @@ -2655,9 +2655,9 @@
"\n",
"Brilliant. I love these stories!!\n",
"\n",
"Anyway, we're paying a poker variant. Our input contains five-card hands, along with bid amounts, e.g.\n",
"Anyway, we're paying a poker variant. Our input contains a list of five-card hands, along with bid amounts, e.g.\n",
"\n",
"```\n",
"```text\n",
"32T3K 765\n",
"T55J5 684\n",
"KK677 28\n",
Expand All @@ -2671,21 +2671,22 @@
"\n",
"Rules for determining hand strengh:\n",
"\n",
"- Usual poker hand strengths, i.e.\n",
" ```\n",
" 5-of-a-kind > 4-of-a-kind\n",
" > full-house\n",
" > 3-of-a-kind\n",
" > two pair\n",
" > one pair\n",
" > high card\n",
" ```\n",
"- But unlike normal poker, hand strength tie-breakers are achieved by comparing the successive card strengths of cards in the hand, starting from first to last.\n",
"- Usual poker hand strengths with straights and flushes removed, i.e.\n",
" \n",
" 1. 5-of-a-kind (\"FIVE\")\n",
" 1. 4-of-a-kind (\"FOUR\")\n",
" 1. full-house (\"FH\")\n",
" 1. 3-of-a-kind (\"THREE\")\n",
" 1. two pair (\"TP\")\n",
" 1. one pair (\"OP\")\n",
" 1. high card (\"HC\")\n",
"\n",
"- But unlike normal poker, hand strength tie-breakers are achieved by comparing the successive card strengths of cards in the hand, going from first to last.\n",
"\n",
"**My solution:**\n",
"\n",
"- Create a `Hand` class:\n",
" - In this class, create a dict class attribute to store `card_scores`. I've assigned integer values so we can compare card strength.\n",
" - In this class, create a dict class attribute to store individual `card_scores`. I've assigned integer values so we can compare card strength.\n",
" - Create a dict class attribute to store `HAND_TYPE`. Again, I've assigned integer values so that we can compare hands.\n",
" - Initialise with a string that represents our five cards.\n",
" - I'm using the Python `collections.Counter` class to help me determine the `HAND_TYPE`. The `Counter` class counts members of any collection passed to it, including strings. It stores the counts as a dict, in the form `{character: count}`. Then we can use `most_common()` to convert this dict into an ordered list of tuples, ordered by count.\n",
Expand Down Expand Up @@ -2721,6 +2722,7 @@
"- When `joker=True`:\n",
" - Update the value of `J` in our `card_scores`. Thus, our `__lt__()` will now work without modification.\n",
" - In `_determine_hand_type()` I've added an extra block that only runs if we're in _joker mode_. This block replaces any `J` in the hand with the card type that is most common. (If the card type that is most common is the `J` itself, then we instead identify the next most common card type.) We then recount the cards, and then perform the same logic as before to determine the hand type.\n",
"- I've made `card_scores` an instance attribute, rather than a class attribute. This is because if we leave it as a class attribute, then once we modify the value of `J`, we can no longer use it to solve Part 1. (E.g. if we were to run the solutions out of order.)\n",
"\n",
"So that's it! Very little change required for Part 2."
]
Expand All @@ -2732,42 +2734,27 @@
"outputs": [],
"source": [
"class Hand:\n",
" card_scores = { # card labels\n",
" \"A\": 14, \n",
" \"K\": 13,\n",
" \"Q\": 12,\n",
" \"J\": 11,\n",
" \"T\": 10,\n",
" \"9\": 9,\n",
" \"8\": 8,\n",
" \"7\": 7,\n",
" \"6\": 6,\n",
" \"5\": 5,\n",
" \"4\": 4,\n",
" \"3\": 3,\n",
" \"2\": 2\n",
" }\n",
" \n",
" HAND_TYPE = {\n",
" \"FIVE\": 7,\n",
" \"FOUR\": 6,\n",
" \"FH\": 5,\n",
" \"THREE\": 4,\n",
" \"TP\": 3,\n",
" \"OP\": 2,\n",
" \"HC\": 1\n",
" }\n",
" # class attributes - never changes\n",
" HAND_TYPE = { hand_type: val for val, hand_type \n",
" in enumerate(\"HC OP TP THREE FH FOUR FIVE\".split(), start=1)}\n",
" \n",
" def __init__(self, cards: str, joker=False) -> None:\n",
" \"\"\" Args:\n",
" cards (str): A str of five chars representating a hand\n",
" joker (bool, optional): Whether J is a Jack or a Joker. Defaults to False.\n",
" \"\"\"\n",
" self.card_scores = { card: val for val, card in enumerate(\"23456789TJQKA\", start=2) }\n",
" \n",
" self.cards = cards\n",
" self._joker = joker\n",
" if self._joker:\n",
" Hand.card_scores[\"J\"] = 1\n",
" self.card_scores[\"J\"] = 1 # update J value\n",
" \n",
" self._hand_type = self._determine_hand_type()\n",
" \n",
" def _determine_hand_type(self):\n",
" cards = self.cards # we will replace value of joker\n",
" def _determine_hand_type(self) -> str:\n",
" \"\"\" Determine HAND TYPE as str \"\"\"\n",
" cards = self.cards\n",
" ordered_counts = Counter(cards).most_common() # e.g. [('3', 2), ('2', 1), ('T', 1), ('K', 1)]\n",
" \n",
" # get the most common card\n",
Expand All @@ -2790,42 +2777,34 @@
" return \"FIVE\"\n",
" \n",
" second_best, second_best_count = ordered_counts[1] # e.g. ('2', 1) \n",
" \n",
" if best_count == 4:\n",
" return \"FOUR\"\n",
" \n",
" if best_count == 3:\n",
" if second_best_count == 2:\n",
" return \"FH\"\n",
" else:\n",
" return \"THREE\"\n",
" \n",
" if best_count == 2:\n",
" if second_best_count == 2:\n",
" return \"TP\"\n",
" else:\n",
" return \"OP\"\n",
"\n",
" return \"HC\"\n",
" \n",
" def value(self):\n",
" \n",
" match best_count: # implement a switch-case\n",
" case 4:\n",
" return \"FOUR\"\n",
" case 3:\n",
" return \"FH\" if second_best_count == 2 else \"THREE\"\n",
" case 2:\n",
" return \"TP\" if second_best_count == 2 else \"OP\"\n",
" case _:\n",
" return \"HC\"\n",
" \n",
" def hand_value(self):\n",
" \"\"\" Return a score, based on hand type \"\"\"\n",
" return Hand.HAND_TYPE[self._hand_type]\n",
" \n",
" def __lt__(self, other: Hand):\n",
" \"\"\" Compare this hand with another hand.\n",
" Winning hand has stronger hand type; for hand type ties, \n",
" winning hand is determined by highest card, starting with the first card in the hand. \n",
" \"\"\"\n",
" if self.value() != other.value():\n",
" return self.value() < other.value()\n",
" \"\"\" Compare this hand with another hand. Winning hand has stronger hand type. \n",
" For hand type ties, winning hand is determined by highest card, \n",
" starting with the first card in the hand. \"\"\"\n",
" if self.hand_value() != other.hand_value():\n",
" return self.hand_value() < other.hand_value()\n",
" \n",
" assert self.value() == other.value(), \"Hand types are the same\"\n",
" assert self.hand_value() == other.hand_value(), \"Hand types are the same\"\n",
" for this_card, other_card in zip(self.cards, other.cards):\n",
" if this_card == other_card:\n",
" continue\n",
" \n",
" return Hand.card_scores[this_card] < Hand.card_scores[other_card]\n",
" return self.card_scores[this_card] < self.card_scores[other_card]\n",
" \n",
" assert False, \"We should not get here\"\n",
" \n",
Expand All @@ -2843,20 +2822,17 @@
"outputs": [],
"source": [
"def solve(data, joker=False):\n",
" \"\"\" For Part 2, we set joker=True \"\"\"\n",
" hands_and_bids = []\n",
" for line in data:\n",
" cards, bid = line.split()\n",
" hand = Hand(cards, joker)\n",
" hands_and_bids.append((hand, int(bid)))\n",
" hands_and_bids.append((hand, int(bid))) # [ (hand, bid), ... ]\n",
" \n",
" hands_and_bids = sorted(hands_and_bids, key=lambda x: x[0])\n",
" \n",
" total_winnings = 0\n",
" for i, (hand, bid) in enumerate(hands_and_bids):\n",
" # logger.debug(f\"Hand: {hand}, bid={bid}\")\n",
" total_winnings += (i+1)*bid\n",
" \n",
" return total_winnings"
" \n",
" return sum(rank*bid for rank, (hand, bid) in enumerate(hands_and_bids, start=1))\n",
" "
]
},
{
Expand Down

0 comments on commit 4a5880d

Please sign in to comment.