comp10001-project03/old/project03-sample-solutions.py
2024-06-08 21:41:57 +10:00

1076 lines
30 KiB
Python

# ------------------------------------------------------------------------------
# Part 1 - Valid table
VALUE = 0
VALUES = {'A': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8,
'9': 9, '0': 10, 'J': 11, 'Q': 12, 'K': 13}
SUIT = COLOUR = 1
COLOURS = {'D': True, 'H': True, 'C': False, 'S': False}
N_SUITS = 4
def comp10001huxxy_valid_table(groups):
""" Returns a boolean indicating whether list of groups `groups`
(containing card strings) represents a valid table.
"""
# Checks each group for validity conditions
for group in groups:
if not valid_group(group):
return False
return True
def valid_group(group):
""" Returns a boolean indicating whether list of card strings `group`
represents a valid group: either a run or n-of-a-kind.
"""
# Groups must be either empty or of length three or greater
if len(group) == 0:
return True
elif len(group) < 3:
return False
else:
return valid_run(group) or valid_n_of_a_kind(group)
def valid_run(group):
""" Returns a boolean indicating whether list of card strings `group`
(which is of a valid length) represents a valid run.
"""
# Generates list of cards in format (value: int, is_red: bool) so that it
# can be sorted by card value
cards = []
for card in group:
curr_value = VALUES[card[VALUE]]
curr_colour = COLOURS[card[SUIT]]
cards.append((curr_value, curr_colour))
cards.sort()
# Processes cards one-by-one, checking for violation of run group rules
prev_card = cards[0]
for card in cards[1:]:
# Value increases by 1 for adjacent cards in the run
if card[VALUE] != prev_card[VALUE] + 1 or \
card[COLOUR] == prev_card[COLOUR]:
return False
prev_card = card
return True
def valid_n_of_a_kind(group):
""" Returns a boolean indicating whether list of card strings `group`
(which is of a valid length) represents a valid n-of-a-kind group.
"""
# Checks that each card has the same value, while adding suits to set
value = group[0][VALUE]
suits = {group[0][SUIT]}
for card in group[1:]:
if card[VALUE] != value:
return False
suits.add(card[SUIT])
# Checks that a group of four or less cards contains no duplicate suits
if len(group) <= N_SUITS:
return len(suits) == len(group)
# Checks that a group of more than four contains one of each suit
else:
return len(suits) == N_SUITS
# ------------------------------------------------------------------------------
# Part 2 - Group validation
# Sample solution 1
from math import factorial
# index of value of a card
VALUE = 0
# index of suit of a card
SUIT = 1
# value of Ace
ACE = 'A'
# dictionary of scores of individual cards
card_score = {
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'0': 10,
'J': 11,
'Q': 12,
'K': 13,
ACE: 20,
}
# suits which are red
RED_SUITS = 'HD'
# suits which are black
BLACK_SUITS = 'SC'
# card colours
RED = 1
BLACK = 2
# minimum no. of cards in an n-of-a-kind set
MIN_CARDS_NKIND = 2
# minimum no. of non-Ace cards in a run
MIN_NONACE_RUN = 2
# minimum no. cards in a run
MIN_RUN = 3
def is_ace(card):
"""Boolean evaluation of whether `card` is an Ace"""
return card[VALUE] == ACE
def get_score(card):
"""return the score of `card`, based on its value"""
return card_score[card[VALUE]]
def get_colour(card):
"""Return the colour of `card` (`RED` or `BLACK`)"""
if card[SUIT] in RED_SUITS:
return RED
else:
return BLACK
def comp10001go_score_group(cards):
"""Validate/score a group of cards (order unimportant), supplied as a
list of cards (each a string); return the positive score of the group if
valid, and negative score otherwise. Note, assumes that all cards are
valid, and unique."""
# construct sorted list of values of cards (ignore suit for now)
values = sorted([get_score(card) for card in cards])
# CASE 1: N-of-a-kind if all cards of same value, at least
# `MIN_CARDS_NKIND` cards in total, and not Aces
if (len(set(values)) == 1 and len(cards) >= MIN_CARDS_NKIND
and not is_ace(cards[0])):
return factorial(len(cards)) * card_score[cards[0][VALUE]]
# construct sorted list of non-Ace cards
nonace_cards = sorted([card for card in cards if not is_ace(card)],
key=lambda x: get_score(x))
# construct list of Ace cards
ace_cards = list(set(cards) - set(nonace_cards))
# run must have at least `MIN_NONACE_RUN` non-Ace cards in it
if len(nonace_cards) >= MIN_NONACE_RUN:
is_run = True
prev_val = prev_colour = None
score = 0
# iterate through cards to make sure they form a run
for card in nonace_cards:
# CASE 1: for the first card in `nonace_cards`, nothing to
# check for
if prev_val is None:
score = prev_val = get_score(card)
prev_colour = get_colour(card)
# CASE 2: adjacent to previous card in value
elif get_score(card) - prev_val == 1:
# CASE 2.1: alternating colour, meaning continuation of run
if get_colour(card) != prev_colour:
prev_val = get_score(card)
prev_colour = get_colour(card)
score += prev_val
# CASE 2.2: not alternating colour, meaning invalid run
else:
is_run = False
break
# CASE 3: repeat value, meaning no possibility of valid run
elif get_score(card) == prev_val:
is_run = False
break
# CASE 4: gap in values, in which case check to see if can be
# filled with Ace(s)
else:
gap = get_score(card) - prev_val - 1
gap_filled = False
# continue until gap filled
while is_run and gap and len(ace_cards) >= gap:
gap_filled = False
# search for an Ace of appropriate colour, and remove
# from list of Aces if found (note that it doesn't matter
# which Ace is used if multiple Aces of same colour)
for i, ace in enumerate(ace_cards):
if get_colour(ace) != prev_colour:
ace_cards.pop(i)
prev_val += 1
prev_colour = get_colour(ace)
score += prev_val
gap -= 1
gap_filled = True
break
if not gap_filled:
is_run = False
if is_run and gap_filled and get_colour(card) != prev_colour:
prev_val = get_score(card)
prev_colour = get_colour(card)
score += prev_val
else:
is_run = False
if is_run and len(cards) >= MIN_RUN and not ace_cards:
return score
return -sum(values)
def comp10001go_valid_groups(groups):
for cards in groups:
if not cards or (len(cards) > 1
and comp10001go_score_group(cards) < 0):
return False
return True
# Sample solution 2
import math
SUIT_TO_COLOUR = dict(zip('HDCS', 'RRBB'))
VALUE_STRING_TO_VALUE = dict(zip('A234567890JQK', range(1, 14)))
VALUE_TO_VALUE_STRING = {v: k for k, v in VALUE_STRING_TO_VALUE.items()}
class Card:
def __init__(self, card_string):
if isinstance(card_string, tuple):
card_string = VALUE_TO_VALUE_STRING[card_string[0]] + card_string[1]
self.value_str = card_string[0]
self.value = VALUE_STRING_TO_VALUE[self.value_str]
self.suit = card_string[1]
self.colour = SUIT_TO_COLOUR[self.suit]
self.inv_colour = 'R' if self.colour == 'B' else 'B'
self.orphan_value = -20 if self.is_ace() else -self.value
def __eq__(self, other):
return self.value_str == other.value_str and self.suit == other.suit
def __repr__(self):
return f'Card(\'{self.value_str}{self.suit}\')'
def __str__(self):
return f'{self.value_str}{self.suit}'
def is_ace(self):
return self.value_str == 'A'
def is_black(self):
return self.colour == 'B'
def is_king(self):
return self.value_str == 'K'
def is_red(self):
return self.colour == 'R'
def construct_n_of_a_kind(cards):
# Early bail if we don't have enough cards.
if len(cards) < 2:
return None
# Ensure that all of the cards have the same value and are not an Ace.
value = None
for card in cards:
if card.is_ace():
return None
elif value is None:
value = card.value
elif card.value != value:
return None
# Return the cards as is.
return list(cards)
def construct_run(cards):
# Early bail if we don't have enough cards.
if len(cards) < 3:
return None
# Partition the cards into Aces and non-Aces.
non_aces = []
aces_by_colour = {'B': [], 'R': []}
for card in cards:
if card.is_ace():
aces_by_colour[card.colour].append(card)
else:
non_aces.append(card)
# Ensure we have enough non-Aces.
if len(non_aces) < 2:
return None
# Sort the non-Aces by value.
non_aces.sort(key=lambda card: card.value)
# Attempt to construct a valid run from the avaialble cards.
prev = non_aces.pop(0)
run = [prev]
while non_aces:
top = non_aces[0]
# Check for a normal valid transition.
if prev.value + 1 == top.value and prev.colour == top.inv_colour:
run.append(non_aces.pop(0)) # Consume the current card in the run.
prev = top
else:
# Check if we can do an Ace insertion.
aces = aces_by_colour[prev.inv_colour]
if aces and not prev.is_king(): # Can't go higher than a King for Ace insertion.
ace = aces.pop(0) # Consume the next Ace.
run.append(ace)
prev = Card((prev.value + 1, ace.suit))
else:
# We did not find a valid transition.
return None
# If we have any aces left over, we do not have a valid run.
if aces_by_colour['B'] or aces_by_colour['R']:
return None
return run
def score_n_of_a_kind(cards):
return cards[0].value * math.factorial(len(cards))
def score_orphans(cards):
return sum(map(lambda card: card.orphan_value, cards))
def score_run(cards):
return sum(range(cards[0].value, cards[-1].value + 1))
def comp10001go_valid_groups(group_strings):
# Convert the card strings to Card objects.
card_groups = [list(map(Card, card_strings)) for card_strings in group_strings]
# Attempt to shape each of the groups of cards into the three allowed shapes.
for cards in card_groups:
if construct_n_of_a_kind(cards) is not None:
pass
elif construct_run(cards) is not None:
pass
elif len(cards) == 1:
pass
else:
return False
# If all groups of cards were one of the three allowed shapes, we're good.
return True
# ------------------------------------------------------------------------------
# Part 3 - Play and Group!
# Sample solution 1
from math import factorial
# index of value of a card
VALUE = 0
# index of suit of a card
SUIT = 1
# value of Ace
ACE = 'A'
# dictionary of scores of individual cards
card_score = {
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'0': 10,
'J': 11,
'Q': 12,
'K': 13,
ACE: 20,
}
# suits which are red
RED_SUITS = 'HD'
# suits which are black
BLACK_SUITS = 'SC'
# card colours
RED = 1
BLACK = 2
# minimum no. of cards in an n-of-a-kind set
MIN_CARDS_NKIND = 2
# minimum no. of non-Ace cards in a run
MIN_NONACE_RUN = 2
# minimum no. cards in a run
MIN_RUN = 3
def is_ace(card):
"""Boolean evaluation of whether `card` is an Ace"""
return card[VALUE] == ACE
def get_score(card):
"""return the score of `card`, based on its value"""
return card_score[card[VALUE]]
def get_colour(card):
"""Return the colour of `card` (`RED` or `BLACK`)"""
if card[SUIT] in RED_SUITS:
return RED
else:
return BLACK
def comp10001go_score_group(cards):
"""Validate/score a group of cards (order unimportant), supplied as a
list of cards (each a string); return the positive score of the group if
valid, and negative score otherwise. Note, assumes that all cards are
valid, and unique."""
# construct sorted list of values of cards (ignore suit for now)
values = sorted([get_score(card) for card in cards])
# CASE 1: N-of-a-kind if all cards of same value, at least
# `MIN_CARDS_NKIND` cards in total, and not Aces
if (len(set(values)) == 1 and len(cards) >= MIN_CARDS_NKIND
and not is_ace(cards[0])):
return factorial(len(cards)) * card_score[cards[0][VALUE]]
# construct sorted list of non-Ace cards
nonace_cards = sorted([card for card in cards if not is_ace(card)],
key=lambda x: get_score(x))
# construct list of Ace cards
ace_cards = list(set(cards) - set(nonace_cards))
# run must have at least `MIN_NONACE_RUN` non-Ace cards in it
if len(nonace_cards) >= MIN_NONACE_RUN:
is_run = True
prev_val = prev_colour = None
score = 0
# iterate through cards to make sure they form a run
for card in nonace_cards:
# CASE 1: for the first card in `nonace_cards`, nothing to
# check for
if prev_val is None:
score = prev_val = get_score(card)
prev_colour = get_colour(card)
# CASE 2: adjacent to previous card in value
elif get_score(card) - prev_val == 1:
# CASE 2.1: alternating colour, meaning continuation of run
if get_colour(card) != prev_colour:
prev_val = get_score(card)
prev_colour = get_colour(card)
score += prev_val
# CASE 2.2: not alternating colour, meaning invalid run
else:
is_run = False
break
# CASE 3: repeat value, meaning no possibility of valid run
elif get_score(card) == prev_val:
is_run = False
break
# CASE 4: gap in values, in which case check to see if can be
# filled with Ace(s)
else:
gap = get_score(card) - prev_val - 1
gap_filled = False
# continue until gap filled
while is_run and gap and len(ace_cards) >= gap:
gap_filled = False
# search for an Ace of appropriate colour, and remove
# from list of Aces if found (note that it doesn't matter
# which Ace is used if multiple Aces of same colour)
for i, ace in enumerate(ace_cards):
if get_colour(ace) != prev_colour:
ace_cards.pop(i)
prev_val += 1
prev_colour = get_colour(ace)
score += prev_val
gap -= 1
gap_filled = True
break
if not gap_filled:
is_run = False
if is_run and gap_filled and get_colour(card) != prev_colour:
prev_val = get_score(card)
prev_colour = get_colour(card)
score += prev_val
else:
is_run = False
if is_run and len(cards) >= MIN_RUN and not ace_cards:
return score
return -sum(values)
def comp10001go_valid_groups(groups):
for cards in groups:
if not cards or (len(cards) > 1
and comp10001go_score_group(cards) < 0):
return False
return True
def comp10001go_score_groups(groups):
score = 0
for group in groups:
score += comp10001go_score_group(group)
return score
def comp10001go_randplay(discard_history, player_no, hand):
from random import shuffle
shuffle(hand)
# for first turn, select lowest card
return hand[0]
def comp10001go_play(discard_history, player_no, hand):
# for first turn, select lowest card
if not discard_history:
return sorted(hand, key=lambda x: get_score(x))[0]
# for subseuquent rounds, select card which maximises optimal score
else:
return sorted(hand, key=lambda x: get_score(x))[0]
def comp10001go_group(discard_history, player_no):
# construct list of discards from `discard_history`
discards = []
for turn in discard_history:
discards.append(turn[player_no])
return [[card] for card in discards]
# Sample solution 2
SUIT_TO_COLOUR = dict(zip('HDCS', 'RRBB'))
VALUE_STRING_TO_VALUE = dict(zip('A234567890JQK', range(1, 14)))
VALUE_TO_VALUE_STRING = {v: k for k, v in VALUE_STRING_TO_VALUE.items()}
class Card:
def __init__(self, card_string):
if isinstance(card_string, tuple):
card_string = VALUE_TO_VALUE_STRING[card_string[0]] + card_string[1]
self.value_str = card_string[0]
self.value = VALUE_STRING_TO_VALUE[self.value_str]
self.suit = card_string[1]
self.colour = SUIT_TO_COLOUR[self.suit]
self.inv_colour = 'R' if self.colour == 'B' else 'B'
self.orphan_value = -20 if self.is_ace() else -self.value
def __eq__(self, other):
return self.value_str == other.value_str and self.suit == other.suit
def __repr__(self):
return f'Card(\'{self.value_str}{self.suit}\')'
def __str__(self):
return f'{self.value_str}{self.suit}'
def is_ace(self):
return self.value_str == 'A'
def is_black(self):
return self.colour == 'B'
def is_king(self):
return self.value_str == 'K'
def is_red(self):
return self.colour == 'R'
def comp10001go_play(discard_history, player_no, hand):
# Convert the card strings to Card objects.
discard_history = [list(map(Card, card_strings)) for card_strings in discard_history]
hand = list(map(Card, hand))
# Greedily discard the smallest valued card in my hand.
hand.sort(key=lambda card: card.orphan_value)
return str(hand[0])
def comp10001go_group(discard_history, player_no):
return [[discards[player_no]] for discards in discard_history]
# ------------------------------------------------------------------------------
# Part 4 - Optimal grouping
# Sample solution 1
from math import factorial
# index of value of a card
VALUE = 0
# index of suit of a card
SUIT = 1
# value of Ace
ACE = 'A'
# dictionary of scores of individual cards
card_score = {
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'0': 10,
'J': 11,
'Q': 12,
'K': 13,
ACE: 20,
}
# suits which are red
RED_SUITS = 'HD'
# suits which are black
BLACK_SUITS = 'SC'
# card colours
RED = 1
BLACK = 2
# minimum no. of cards in an n-of-a-kind set
MIN_CARDS_NKIND = 2
# minimum no. of non-Ace cards in a run
MIN_NONACE_RUN = 2
# minimum no. cards in a run
MIN_RUN = 3
def is_ace(card):
"""Boolean evaluation of whether `card` is an Ace"""
return card[VALUE] == ACE
def get_score(card):
"""return the score of `card`, based on its value"""
return card_score[card[VALUE]]
def get_colour(card):
"""Return the colour of `card` (`RED` or `BLACK`)"""
if card[SUIT] in RED_SUITS:
return RED
else:
return BLACK
def comp10001go_score_group(cards):
"""Validate/score a group of cards (order unimportant), supplied as a
list of cards (each a string); return the positive score of the group if
valid, and negative score otherwise. Note, assumes that all cards are
valid, and unique."""
# construct sorted list of values of cards (ignore suit for now)
values = sorted([get_score(card) for card in cards])
# CASE 1: N-of-a-kind if all cards of same value, at least
# `MIN_CARDS_NKIND` cards in total, and not Aces
if (len(set(values)) == 1 and len(cards) >= MIN_CARDS_NKIND
and not is_ace(cards[0])):
return factorial(len(cards)) * card_score[cards[0][VALUE]]
# construct sorted list of non-Ace cards
nonace_cards = sorted([card for card in cards if not is_ace(card)],
key=lambda x: get_score(x))
# construct list of Ace cards
ace_cards = list(set(cards) - set(nonace_cards))
# run must have at least `MIN_NONACE_RUN` non-Ace cards in it
if len(nonace_cards) >= MIN_NONACE_RUN:
is_run = True
prev_val = prev_colour = None
score = 0
# iterate through cards to make sure they form a run
for card in nonace_cards:
# CASE 1: for the first card in `nonace_cards`, nothing to
# check for
if prev_val is None:
score = prev_val = get_score(card)
prev_colour = get_colour(card)
# CASE 2: adjacent to previous card in value
elif get_score(card) - prev_val == 1:
# CASE 2.1: alternating colour, meaning continuation of run
if get_colour(card) != prev_colour:
prev_val = get_score(card)
prev_colour = get_colour(card)
score += prev_val
# CASE 2.2: not alternating colour, meaning invalid run
else:
is_run = False
break
# CASE 3: repeat value, meaning no possibility of valid run
elif get_score(card) == prev_val:
is_run = False
break
# CASE 4: gap in values, in which case check to see if can be
# filled with Ace(s)
else:
gap = get_score(card) - prev_val - 1
gap_filled = False
# continue until gap filled
while is_run and gap and len(ace_cards) >= gap:
gap_filled = False
# search for an Ace of appropriate colour, and remove
# from list of Aces if found (note that it doesn't matter
# which Ace is used if multiple Aces of same colour)
for i, ace in enumerate(ace_cards):
if get_colour(ace) != prev_colour:
ace_cards.pop(i)
prev_val += 1
prev_colour = get_colour(ace)
score += prev_val
gap -= 1
gap_filled = True
break
if not gap_filled:
is_run = False
if is_run and gap_filled and get_colour(card) != prev_colour:
prev_val = get_score(card)
prev_colour = get_colour(card)
score += prev_val
else:
is_run = False
if is_run and len(cards) >= MIN_RUN and not ace_cards:
return score
return -sum(values)
def comp10001go_valid_groups(groups):
for cards in groups:
if not cards or (len(cards) > 1
and comp10001go_score_group(cards) < 0):
return False
return True
def comp10001go_score_groups(groups):
score = 0
for group in groups:
score += comp10001go_score_group(group)
return score
def comp10001go_randplay(discard_history, player_no, hand):
from random import shuffle
shuffle(hand)
# for first turn, select lowest card
return hand[0]
def comp10001go_partition(cards):
# BASE CASE 1: no cards, so no grouping to make
if len(cards) == 0:
return []
# BASE CASE 2: single card, so make a singleton group
if len(cards) == 1:
return [[cards]]
# RECURSIVE CASE
out = []
first = cards[0]
for sub_partition in comp10001go_partition(cards[1:]):
# insert `first` in each of the subpartition's groups
for n, subpart in enumerate(sub_partition):
out.append(sub_partition[:n] + [[first] + subpart] + sub_partition[n+1:])
# put `first` in its own subpart
out.append([[first]] + sub_partition)
return out
def comp10001go_best_partitions(cards):
# generate and score all valid card groups from `cards`
valid_groups = [(part, comp10001go_score_groups(part)) for part in comp10001go_partition(cards)
if comp10001go_valid_groups(part)]
if valid_groups:
first_group, best_score = valid_groups[0]
best_groups = [first_group]
for group, score in valid_groups[1:]:
if score > best_score:
best_groups = [group]
best_score = score
elif score == best_score:
best_groups.append(group)
return best_groups
# Sample solution 2
import itertools
import math
SUIT_TO_COLOUR = dict(zip('HDCS', 'RRBB'))
VALUE_STRING_TO_VALUE = dict(zip('A234567890JQK', range(1, 14)))
VALUE_TO_VALUE_STRING = {v: k for k, v in VALUE_STRING_TO_VALUE.items()}
class Card:
def __init__(self, card_string):
if isinstance(card_string, Card):
card_string = str(card_string)
elif isinstance(card_string, tuple):
card_string = VALUE_TO_VALUE_STRING[card_string[0]] + card_string[1]
self.value_str = card_string[0]
self.value = VALUE_STRING_TO_VALUE[self.value_str]
self.suit = card_string[1]
self.colour = SUIT_TO_COLOUR[self.suit]
self.inv_colour = 'R' if self.colour == 'B' else 'B'
self.orphan_value = -20 if self.is_ace() else -self.value
def __hash__(self):
return hash(str(self))
def __eq__(self, other):
return self.value_str == other.value_str and self.suit == other.suit
def __repr__(self):
return f'Card(\'{self.value_str}{self.suit}\')'
def __str__(self):
return f'{self.value_str}{self.suit}'
def is_ace(self):
return self.value_str == 'A'
def is_black(self):
return self.colour == 'B'
def is_king(self):
return self.value_str == 'K'
def is_red(self):
return self.colour == 'R'
def construct_n_of_a_kind(cards):
# Early bail if we don't have enough cards.
if len(cards) < 2:
return None
# Ensure that all of the cards have the same value and are not an Ace.
value = None
for card in cards:
if card.is_ace():
return None
elif value is None:
value = card.value
elif card.value != value:
return None
# Return the cards as is.
return list(cards)
def construct_run(cards):
# Early bail if we don't have enough cards.
if len(cards) < 3:
return None
# Partition the cards into Aces and non-Aces.
non_aces = []
aces_by_colour = {'B': [], 'R': []}
for card in cards:
if card.is_ace():
aces_by_colour[card.colour].append(card)
else:
non_aces.append(card)
# Ensure we have enough non-Aces.
if len(non_aces) < 2:
return None
# Sort the non-Aces by value.
non_aces.sort(key=lambda card: card.value, reverse=True)
# Attempt to construct a valid run from the avaialble cards.
prev = non_aces.pop()
run = [prev]
while non_aces:
top = non_aces[-1]
# Check for a normal valid transition.
if prev.value + 1 == top.value and prev.colour == top.inv_colour:
run.append(non_aces.pop()) # Consume the current card in the run.
prev = top
else:
# Check if we can do an Ace insertion.
aces = aces_by_colour[prev.inv_colour]
if aces and not prev.is_king(): # Can't go higher than a King for Ace insertion.
ace = aces.pop() # Consume the next ace.
run.append(ace)
prev = Card((prev.value + 1, ace.suit))
else:
# We did not find a valid transition.
return None
# If we have any aces left over, we do not have a valid run.
if aces_by_colour['B'] or aces_by_colour['R']:
return None
return run
def score_n_of_a_kind(cards):
return cards[0].value * math.factorial(len(cards))
def score_orphans(cards):
return sum(map(lambda card: card.orphan_value, cards))
def score_run(cards):
return sum(range(cards[0].value, cards[-1].value + 1))
def score_group(cards):
if len(cards) == 1:
return score_orphans(cards)
grouped_cards = construct_n_of_a_kind(cards)
if grouped_cards is not None:
return score_n_of_a_kind(grouped_cards)
grouped_cards = construct_run(cards)
if grouped_cards is not None:
return score_run(grouped_cards)
assert False, 'Should not be possible'
def is_valid_group(cards):
if len(cards) == 1:
return True
elif construct_n_of_a_kind(cards) is not None:
return True
elif construct_run(cards) is not None:
return True
else:
return False
def _generate_partitions(cards, groups):
if len(cards) == 0:
yield frozenset(groups)
return
for k in range(1, len(cards) + 1):
for combination in map(frozenset, itertools.combinations(cards, k)):
if not is_valid_group(combination):
continue
remaining_cards = cards - combination
groups.append(combination)
yield from _generate_partitions(remaining_cards, groups)
groups.pop()
def generate_partitions(cards):
seen = set()
for partition in _generate_partitions(frozenset(cards), []):
if partition in seen:
continue
seen.add(partition)
yield list(map(list, partition))
def comp10001go_best_partitions(card_strings):
cards = set(map(Card, card_strings))
max_score = float('-inf')
max_partitions = []
for partition in generate_partitions(cards):
score = sum(map(score_group, partition))
if score > max_score:
max_score = score
max_partitions = [partition]
elif score == max_score:
max_partitions.append(partition)
return [[list(map(str, cards)) for cards in partition] for partition in max_partitions]