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