Day 7 - Advent of Code 2023

Working solutions for the day 7 puzzles.

Part One

""" day_07_01.py """

# usage: python3 day_07_01.py < input

import functools
import sys


def arrange(hand, sort=True):
    """ format """
    ref = {'A': 14, 'K': 13, 'Q': 12, 'J': 11, 'T': 10, '9': 9,
           '8': 8, '7': 7, '6': 6, '5': 5, '4': 4, '3': 3, '2': 2}
    x = [ref[i] for i in hand]
    if sort:
        return sorted(x)
    return x


def x_of_a_kind(x, hand):
    """ x of a kind """
    for i in range(6 - x):
        if len(set(hand[i:i + x])) == 1:
            return True
    return False


def five_of_a_kind(hand):
    """ five of a kind """
    return x_of_a_kind(5, hand)


def full_house(hand):
    """ full house """
    if not three_of_a_kind(hand):
        return False
    return len(set(hand)) == 2


def four_of_a_kind(hand):
    """ four of a kind """
    return x_of_a_kind(4, hand)


def three_of_a_kind(hand):
    """ three of a kind """
    return x_of_a_kind(3, hand)


def two_pair(hand):
    """ two pair """
    if not two_of_a_kind(hand):
        return False
    return len(set(hand)) == 3


def two_of_a_kind(hand):
    """ two of a kind """
    return x_of_a_kind(2, hand)


def high(hand1, hand2):
    """ hand1 has high card per puzzle description """
    h1, h2 = arrange(hand1[0], sort=False), arrange(hand2[0], sort=False)
    for x, y in zip(h1, h2):
        if x > y:
            return 1
        if y > x:
            return -1
    return 0


def wins(hand1, hand2):
    """ hand 1 wins over hand 2 """
    def helper(func):
        """ win, lose or draw func """
        x, y, z = func(h1), func(h2), high(hand1, hand2)
        ref = {(True, True): z, (True, False): 1,
               (False, True): -1, (False, False): 0}
        return ref[(x, y)]

    h1, h2 = arrange(hand1[0]), arrange(hand2[0])

    for f in [five_of_a_kind, four_of_a_kind, full_house,
              three_of_a_kind, two_pair, two_of_a_kind]:

        if (outcome := helper(f)) != 0:
            return outcome

    return high(hand1, hand2)


hands = [line.split() for line in sys.stdin]
hands.sort(key=functools.cmp_to_key(wins))

print(sum((i + 1) * int(bid) for i, (_, bid) in enumerate(hands)))

Part Two

""" day_07_02.py """

# usage: python3 day_07_02.py < input

import functools
import sys


def arrange(hand, sort=True):
    """ format """
    ref = {'A': 14, 'K': 13, 'Q': 12, 'J': 1, 'T': 10, '9': 9,
           '8': 8, '7': 7, '6': 6, '5': 5, '4': 4, '3': 3, '2': 2}
    x = [ref[i] for i in hand]
    if sort:
        return sorted(x)
    return x


def x_of_a_kind(x, hand):
    """ x of a kind """
    h = [i for i in hand if i != 1]
    for i in range(6 - x):
        if len(set(h[i:i + (x - (5 - len(h)))])) == 1:
            return True
    return hand == [1, 1, 1, 1, 1]


def five_of_a_kind(hand):
    """ five of a kind """
    return x_of_a_kind(5, hand)


def four_of_a_kind(hand):
    """ four of a kind """
    return x_of_a_kind(4, hand)


def full_house(hand):
    """ full house """
    if not three_of_a_kind(hand):
        return False
    return len(set(i for i in hand if i != 1)) == 2


def three_of_a_kind(hand):
    """ three of a kind """
    return x_of_a_kind(3, hand)


def two_pair(hand):
    """ two pair """
    if not two_of_a_kind(hand):
        return False
    return len(set(hand)) == 3


def two_of_a_kind(hand):
    """ two of a kind """
    return x_of_a_kind(2, hand)


def high(hand1, hand2):
    """ hand1 has high card per puzzle description """
    h1, h2 = arrange(hand1[0], sort=False), arrange(hand2[0], sort=False)
    for x, y in zip(h1, h2):
        if x > y:
            return 1
        if y > x:
            return -1
    return 0


def wins(hand1, hand2):
    """ hand 1 wins over hand 2 """
    def helper(func):
        """ win, lose or draw func """
        x, y, z = func(h1), func(h2), high(hand1, hand2)
        ref = {(True, True): z, (True, False): 1,
               (False, True): -1, (False, False): 0}
        return ref[(x, y)]

    h1, h2 = arrange(hand1[0]), arrange(hand2[0])

    for f in [five_of_a_kind, four_of_a_kind, full_house,
              three_of_a_kind, two_pair, two_of_a_kind]:

        if (outcome := helper(f)) != 0:
            return outcome

    return high(hand1, hand2)


hands = [line.split() for line in sys.stdin]
hands.sort(key=functools.cmp_to_key(wins))

print(sum((i + 1) * int(bid) for i, (_, bid) in enumerate(hands)))