Solve day 7
This commit is contained in:
14
07/__main__.py
Normal file
14
07/__main__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from pathlib import Path
|
||||
|
||||
from puzzle import solve
|
||||
|
||||
|
||||
def main():
|
||||
input = Path(__file__).parent.joinpath("hands.txt").read_text().strip().split("\n")
|
||||
print("Total Winnings:")
|
||||
print("Part 1 -", solve(input, False))
|
||||
print("Part 2 -", solve(input, True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
145
07/hand.py
Normal file
145
07/hand.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from copy import copy
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
from functools import cached_property
|
||||
|
||||
|
||||
class Strength(IntEnum):
|
||||
FiveOfAKind = 6
|
||||
FourOfAKind = 5
|
||||
FullHouse = 4
|
||||
ThreeOfAKind = 3
|
||||
TwoPair = 2
|
||||
OnePair = 1
|
||||
HighCard = 0
|
||||
|
||||
|
||||
class Card(IntEnum):
|
||||
Joker = 1
|
||||
Two = 2
|
||||
Three = 3
|
||||
Four = 4
|
||||
Five = 5
|
||||
Six = 6
|
||||
Seven = 7
|
||||
Eight = 8
|
||||
Nine = 9
|
||||
Ten = 10
|
||||
Jack = 11
|
||||
Queen = 12
|
||||
King = 13
|
||||
Ace = 14
|
||||
|
||||
@classmethod
|
||||
def parse(cls, symbol: str, joker_mode=False) -> Card:
|
||||
match symbol:
|
||||
case "2":
|
||||
return Card.Two
|
||||
case "3":
|
||||
return Card.Three
|
||||
case "4":
|
||||
return Card.Four
|
||||
case "5":
|
||||
return Card.Five
|
||||
case "6":
|
||||
return Card.Six
|
||||
case "7":
|
||||
return Card.Seven
|
||||
case "8":
|
||||
return Card.Eight
|
||||
case "9":
|
||||
return Card.Nine
|
||||
case "T":
|
||||
return Card.Ten
|
||||
case "J":
|
||||
return Card.Joker if joker_mode else Card.Jack
|
||||
case "Q":
|
||||
return Card.Queen
|
||||
case "K":
|
||||
return Card.King
|
||||
case "A":
|
||||
return Card.Ace
|
||||
raise ValueError(f'The symbol "{symbol}" does not correspond to a valid card.')
|
||||
|
||||
|
||||
hand_pattern = re.compile(r"^([2-9TJQKA]{5}) (\d+)$")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Hand:
|
||||
cards: tuple[Card, Card, Card, Card, Card]
|
||||
bid: int
|
||||
|
||||
@cached_property
|
||||
def _groups(self) -> dict[Card, int]:
|
||||
groups: dict[Card, int] = {}
|
||||
for card in self.cards:
|
||||
groups[card] = groups.get(card, 0) + 1
|
||||
return groups
|
||||
|
||||
@cached_property
|
||||
def _counts(self) -> list[int]:
|
||||
groups = copy(self._groups)
|
||||
if Card.Joker in groups:
|
||||
del groups[Card.Joker]
|
||||
return list(
|
||||
reversed(
|
||||
sorted(count for count in groups.values()),
|
||||
),
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def _jokers(self) -> int:
|
||||
return self._groups.get(Card.Joker, 0)
|
||||
|
||||
@cached_property
|
||||
def strength(self):
|
||||
match (self._counts, self._jokers):
|
||||
case ([5], 0) | ([4], 1) | ([3], 2) | ([2], 3) | ([1], 4) | ([], 5):
|
||||
return Strength.FiveOfAKind
|
||||
case ([4, 1], 0) | ([3, 1], 1) | ([2, 1], 2) | ([1, 1], 3):
|
||||
return Strength.FourOfAKind
|
||||
case ([3, 2], 0) | ([2, 2], 1):
|
||||
return Strength.FullHouse
|
||||
case ([3, 1, 1], 0) | ([2, 1, 1], 1) | ([1, 1, 1], 2):
|
||||
return Strength.ThreeOfAKind
|
||||
case ([2, 2, 1], 0):
|
||||
return Strength.TwoPair
|
||||
case ([2, 1, 1, 1], 0) | ([1, 1, 1, 1], 1):
|
||||
return Strength.OnePair
|
||||
case _:
|
||||
return Strength.HighCard
|
||||
|
||||
@classmethod
|
||||
def parse(self, hand_desc: str, joker_mode=False) -> Hand:
|
||||
match = hand_pattern.match(hand_desc)
|
||||
if not match:
|
||||
raise ValueError(f'Invalid hand description: "{hand_desc}"')
|
||||
return Hand(
|
||||
tuple(Card.parse(symbol, joker_mode) for symbol in match.group(1)), # type: ignore
|
||||
int(match.group(2)),
|
||||
)
|
||||
|
||||
def __lt__(self, other: Hand) -> bool:
|
||||
if self.strength < other.strength:
|
||||
return True
|
||||
if self.strength > other.strength:
|
||||
return False
|
||||
return self.cards < other.cards
|
||||
|
||||
def __gt__(self, other: Hand) -> bool:
|
||||
if self.strength > other.strength:
|
||||
return True
|
||||
if self.strength < other.strength:
|
||||
return False
|
||||
return self.cards > other.cards
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Hand):
|
||||
return False
|
||||
if self.strength != other.strength:
|
||||
return False
|
||||
return self.cards == other.cards
|
269
07/hand_test.py
Normal file
269
07/hand_test.py
Normal file
@@ -0,0 +1,269 @@
|
||||
import pytest
|
||||
from hand import Card, Hand, Strength
|
||||
|
||||
|
||||
def test_card_parse():
|
||||
assert Card.parse("2") == Card.Two
|
||||
assert Card.parse("3") == Card.Three
|
||||
assert Card.parse("4") == Card.Four
|
||||
assert Card.parse("5") == Card.Five
|
||||
assert Card.parse("6") == Card.Six
|
||||
assert Card.parse("7") == Card.Seven
|
||||
assert Card.parse("8") == Card.Eight
|
||||
assert Card.parse("9") == Card.Nine
|
||||
assert Card.parse("T") == Card.Ten
|
||||
assert Card.parse("J") == Card.Jack
|
||||
assert Card.parse("Q") == Card.Queen
|
||||
assert Card.parse("K") == Card.King
|
||||
assert Card.parse("A") == Card.Ace
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
Card.parse("X")
|
||||
|
||||
|
||||
def test_hand_parse():
|
||||
assert Hand.parse("32T3K 765") == Hand(
|
||||
(
|
||||
Card.Three,
|
||||
Card.Two,
|
||||
Card.Ten,
|
||||
Card.Three,
|
||||
Card.King,
|
||||
),
|
||||
765,
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
Hand.parse("invalid")
|
||||
|
||||
|
||||
def test_hand_groups():
|
||||
assert Hand(
|
||||
(
|
||||
Card.Five,
|
||||
Card.Two,
|
||||
Card.Five,
|
||||
Card.Two,
|
||||
Card.Ace,
|
||||
),
|
||||
0,
|
||||
)._groups == {
|
||||
Card.Five: 2,
|
||||
Card.Two: 2,
|
||||
Card.Ace: 1,
|
||||
}
|
||||
assert Hand(
|
||||
(
|
||||
Card.Two,
|
||||
Card.Eight,
|
||||
Card.Nine,
|
||||
Card.Queen,
|
||||
Card.Jack,
|
||||
),
|
||||
0,
|
||||
)._groups == {
|
||||
Card.Two: 1,
|
||||
Card.Eight: 1,
|
||||
Card.Nine: 1,
|
||||
Card.Queen: 1,
|
||||
Card.Jack: 1,
|
||||
}
|
||||
|
||||
|
||||
def test_hand_counts():
|
||||
assert Hand(
|
||||
(
|
||||
Card.Five,
|
||||
Card.Two,
|
||||
Card.Five,
|
||||
Card.Two,
|
||||
Card.Ace,
|
||||
),
|
||||
0,
|
||||
)._counts == [2, 2, 1]
|
||||
assert Hand(
|
||||
(
|
||||
Card.Four,
|
||||
Card.Four,
|
||||
Card.Seven,
|
||||
Card.Four,
|
||||
Card.Seven,
|
||||
),
|
||||
0,
|
||||
)._counts == [3, 2]
|
||||
assert Hand(
|
||||
(
|
||||
Card.Two,
|
||||
Card.Three,
|
||||
Card.Four,
|
||||
Card.Five,
|
||||
Card.Six,
|
||||
),
|
||||
0,
|
||||
)._counts == [1, 1, 1, 1, 1]
|
||||
assert Hand(
|
||||
(
|
||||
Card.Two,
|
||||
Card.Joker,
|
||||
Card.Four,
|
||||
Card.Joker,
|
||||
Card.Six,
|
||||
),
|
||||
0,
|
||||
)._counts == [1, 1, 1]
|
||||
assert (
|
||||
Hand(
|
||||
(
|
||||
Card.Joker,
|
||||
Card.Joker,
|
||||
Card.Joker,
|
||||
Card.Joker,
|
||||
Card.Joker,
|
||||
),
|
||||
0,
|
||||
)._counts
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_hand_card_strength():
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Ace, Card.Ace, Card.Ace, Card.Ace, Card.Ace),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FiveOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Ace, Card.Ace, Card.Ace, Card.Ace, Card.Joker),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FiveOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Ace, Card.Joker, Card.Ace, Card.Ace, Card.Joker),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FiveOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Joker, Card.Joker, Card.Ace, Card.Ace, Card.Joker),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FiveOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Joker, Card.Joker, Card.Ace, Card.Joker, Card.Joker),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FiveOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Joker, Card.Joker, Card.Joker, Card.Joker, Card.Joker),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FiveOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.King, Card.King, Card.King, Card.King, Card.Five),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FourOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.King, Card.Joker, Card.King, Card.King, Card.Five),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FourOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Joker, Card.Joker, Card.King, Card.King, Card.Five),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FourOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Joker, Card.Joker, Card.King, Card.Joker, Card.Five),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FourOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.King, Card.Six, Card.King, Card.King, Card.Six),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FullHouse
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.King, Card.Six, Card.Joker, Card.King, Card.Six),
|
||||
0,
|
||||
).strength
|
||||
== Strength.FullHouse
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Ace, Card.Ace, Card.Two, Card.Five, Card.Ace),
|
||||
0,
|
||||
).strength
|
||||
== Strength.ThreeOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Joker, Card.Ace, Card.Two, Card.Five, Card.Ace),
|
||||
0,
|
||||
).strength
|
||||
== Strength.ThreeOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Joker, Card.Ace, Card.Two, Card.Five, Card.Joker),
|
||||
0,
|
||||
).strength
|
||||
== Strength.ThreeOfAKind
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Five, Card.Two, Card.Five, Card.Two, Card.Ace),
|
||||
0,
|
||||
).strength
|
||||
== Strength.TwoPair
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Five, Card.Two, Card.Five, Card.Nine, Card.Ace),
|
||||
0,
|
||||
).strength
|
||||
== Strength.OnePair
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Five, Card.Two, Card.Joker, Card.Nine, Card.Ace),
|
||||
0,
|
||||
).strength
|
||||
== Strength.OnePair
|
||||
)
|
||||
assert (
|
||||
Hand(
|
||||
(Card.Five, Card.Two, Card.Jack, Card.Nine, Card.Ace),
|
||||
0,
|
||||
).strength
|
||||
== Strength.HighCard
|
||||
)
|
||||
|
||||
|
||||
def test_hand_comparison():
|
||||
hand_a = Hand((Card.Two, Card.Three, Card.Five, Card.Eight, Card.Ace), 1)
|
||||
hand_b = Hand((Card.Ace, Card.Ace, Card.Ace, Card.Ace, Card.Ace), 1)
|
||||
assert hand_a < hand_b
|
||||
assert hand_b > hand_a
|
||||
assert hand_a == hand_a
|
1000
07/hands.txt
Normal file
1000
07/hands.txt
Normal file
File diff suppressed because it is too large
Load Diff
6
07/puzzle.py
Normal file
6
07/puzzle.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from hand import Hand
|
||||
|
||||
|
||||
def solve(hand_descs: list[str], joker_mode=False):
|
||||
hands = sorted(Hand.parse(hand_desc, joker_mode) for hand_desc in hand_descs)
|
||||
return sum((index + 1) * hand.bid for index, hand in enumerate(hands))
|
14
07/puzzle_test.py
Normal file
14
07/puzzle_test.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from puzzle import solve
|
||||
|
||||
mock_hand_descriptions = [
|
||||
"32T3K 765",
|
||||
"T55J5 684",
|
||||
"KK677 28",
|
||||
"KTJJT 220",
|
||||
"QQQJA 483",
|
||||
]
|
||||
|
||||
|
||||
def test_solve():
|
||||
assert solve(mock_hand_descriptions, False) == 6440
|
||||
assert solve(mock_hand_descriptions, True) == 5905
|
Reference in New Issue
Block a user