adventOfCode/challenges/2023/07-camelCards/main.py
AKP bdc95074df
2023.07
Squashed commit of the following:

commit b15750b28c94f534a9a775112b961bdd9cecfde9
Author: AKP <abi@tdpain.net>
Date:   Thu Dec 7 16:55:14 2023 +0000

    Update `README.md`

commit baa6132676ad52a403fe8119cb829f3a9219691a
Author: AKP <abi@tdpain.net>
Date:   Thu Dec 7 16:53:55 2023 +0000

    Oh cool this works

commit 30b802851ae541e5354bc07806d9d576c5990bb5
Author: AKP <abi@tdpain.net>
Date:   Thu Dec 7 16:37:09 2023 +0000

    This kind of works
2023-12-07 16:55:32 +00:00

146 lines
3.3 KiB
Python

import sys
from enum import IntEnum
import functools
from typing import Callable, Optional
def parse(instr: str) -> list[tuple[str, int]]:
return [((sp := x.split(" "))[0], int(sp[1])) for x in instr.splitlines()]
def count_chars(x: str) -> dict[str, int]:
res = {}
for ch in x:
res[ch] = res.get(ch, 0) + 1
return res
class HandType(IntEnum):
HighCard = 1
OnePair = 2
TwoPair = 3
ThreeOfAKind = 4
FullHouse = 5
FourOfAKind = 6
FiveOfAKind = 7
def get_hand_type(hand: str, counts: Optional[dict[str, int]] = None) -> HandType:
if counts is None:
counts = count_chars(hand)
num_unique_chars = len(counts)
if num_unique_chars == 5:
return HandType.HighCard
if num_unique_chars == 4:
return HandType.OnePair
if num_unique_chars == 1:
return HandType.FiveOfAKind
sorted_counts = list(sorted(counts.values()))
if num_unique_chars == 3:
if sorted_counts == [1, 2, 2]:
return HandType.TwoPair
return HandType.ThreeOfAKind
if num_unique_chars == 2:
if sorted_counts == [1, 4]:
return HandType.FourOfAKind
return HandType.FullHouse
raise ValueError(f"bad hand {hand}")
def get_best_hand_type(hand: str) -> HandType:
counts = count_chars(hand)
if "J" in counts:
num_jokers = counts["J"]
if num_jokers == 5:
return HandType.FiveOfAKind
del counts["J"]
most_frequent_card = max(counts, key=lambda x: counts[x])
counts[most_frequent_card] = counts[most_frequent_card] + num_jokers
return get_hand_type(hand, counts=counts)
def compare_card(a: str, b: str, ranking_sequence: list[str]) -> int:
ca, cb = ranking_sequence.index(a), ranking_sequence.index(b)
if ca > cb:
return 1
if ca < cb:
return -1
return 0
def compare_hands(
a: str, b: str, ranking_sequence: list[str], hand_type: Callable[[str], HandType]
) -> int:
hta, htb = hand_type(a), hand_type(b)
if hta > htb:
return 1
if hta < htb:
return -1
for (ca, cb) in zip(a, b):
if ca != cb:
return compare_card(ca, cb, ranking_sequence)
return 0
def run(
instr: str, sequence: list[str], hand_type_fn: Callable[[str], HandType]
) -> int:
@functools.cmp_to_key
def cmp_fn(a: tuple[str, any], b: tuple[str, any]) -> int:
return compare_hands(a[0], b[0], sequence, hand_type_fn)
plays = parse(instr)
acc = 0
for i, (_, bid) in enumerate(sorted(plays, key=cmp_fn)):
acc += bid * (i + 1)
return acc
def one(instr: str):
return run(
instr,
["2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A"],
get_hand_type,
)
def two(instr: str):
return run(
instr,
["J", "2", "3", "4", "5", "6", "7", "8", "9", "T", "Q", "K", "A"],
get_best_hand_type,
)
def _debug(*args, **kwargs):
kwargs["file"] = sys.stderr
print(*args, **kwargs)
if __name__ == "__main__":
if len(sys.argv) < 2 or sys.argv[1] not in ["1", "2"]:
print("Missing day argument", file=sys.stderr)
sys.exit(1)
inp = sys.stdin.read().strip()
if sys.argv[1] == "1":
print(one(inp))
else:
print(two(inp))