| 1 | | def best_hands(hands: list[str]) -> list[str]: # each hand is a string like "4S 5S 7H 8D JC" |
| 2 | | pass |
| 1 | def best_hands(hands: list[str]) -> list[str]: |
| 2 | """ |
| 3 | Pick the best hand(s) from a list of poker hands. |
| 4 | |
| 5 | Args: |
| 6 | hands: List of poker hand strings, where each hand contains exactly 5 cards. |
| 7 | Each card should be in format like 'AS' (Ace of Spades), '10H' (10 of Hearts), etc. |
| 8 | Valid suits: S (Spades), H (Hearts), D (Diamonds), C (Clubs) |
| 9 | Valid values: 2-10, J, Q, K, A |
| 10 | |
| 11 | Returns: |
| 12 | List of the best hand(s) from the input. If multiple hands tie for the best rank, |
| 13 | all winning hands are returned in the order they appeared in the input. |
| 14 | |
| 15 | Raises: |
| 16 | ValueError: If any hand contains invalid card format, invalid suit, invalid value, |
| 17 | or does not contain exactly 5 cards. |
| 18 | |
| 19 | Examples: |
| 20 | >>> best_hands(["AS KS QS JS 10S", "2D 3D 4D 5D 6D"]) |
| 21 | ['AS KS QS JS 10S'] # Royal flush beats straight flush |
| 22 | |
| 23 | >>> best_hands(["AS 2D 3C 4H 5S", "2D 3C 4H 5S 6D"]) |
| 24 | ['2D 3C 4H 5S 6D'] # Higher straight wins |
| 25 | """ |
| 26 | if not hands: |
| 27 | return [] |
| 28 | |
| 29 | # Edge Case: Single hand provided |
| 30 | if len(hands) == 1: |
| 31 | return hands |
| 32 | |
| 33 | ranked_hands = [] |
| 34 | for hand in hands: |
| 35 | ranked_hands.append((hand, rank_hand(parse_hand(hand)))) |
| 36 | |
| 37 | # Find the best rank |
| 38 | best_rank = max(rank for _, rank in ranked_hands) |
| 39 | |
| 40 | # Filter hands with the best rank |
| 41 | best_hands_list = [hand for hand, rank in ranked_hands if rank == best_rank] |
| 42 | |
| 43 | return best_hands_list |
| 44 | |
| 45 | |
| 46 | def parse_hand(hand_str: str) -> list[tuple]: |
| 47 | """ |
| 48 | Parse a poker hand string into a list of card tuples (value, suit). |
| 49 | |
| 50 | Args: |
| 51 | hand_str: String representation of a poker hand (e.g., "AS KS QS JS 10S") |
| 52 | Each card should be in format: value + suit |
| 53 | Valid suits: S (Spades), H (Hearts), D (Diamonds), C (Clubs) |
| 54 | Valid values: 2-10, J, Q, K, A |
| 55 | |
| 56 | Returns: |
| 57 | List of tuples [(value, suit), ...] where value is numeric (2-14) and suit is single character |
| 58 | A=14, K=13, Q=12, J=11, 10-2 remain as numeric values |
| 59 | |
| 60 | Raises: |
| 61 | ValueError: If hand_str is empty, contains invalid card format, invalid suit, |
| 62 | invalid card value, or does not contain exactly 5 cards. |
| 63 | |
| 64 | Examples: |
| 65 | >>> parse_hand("AS KS QS JS 10S") |
| 66 | [(14, 'S'), (13, 'S'), (12, 'S'), (11, 'S'), (10, 'S')] |
| 67 | |
| 68 | >>> parse_hand("2D 3C 4H 5S 6D") |
| 69 | [(6, 'D'), (5, 'S'), (4, 'H'), (3, 'C'), (2, 'D')] |
| 70 | """ |
| 71 | if not hand_str or not hand_str.strip(): |
| 72 | raise ValueError("Hand string cannot be empty") |
| 73 | |
| 74 | cards = hand_str.split() |
| 75 | parsed_cards = [] |
| 76 | |
| 77 | for i, card in enumerate(cards, 1): |
| 78 | if len(card) < 2: |
| 79 | raise ValueError(f"Card {i} has invalid format: '{card}'. Expected format: value + suit (e.g., 'AS', '10H')") |
| 80 | |
| 81 | suit = card[-1].upper() |
| 82 | value_str = card[:-1].upper() |
| 83 | |
| 84 | # Validate suit |
| 85 | if suit not in 'SHDC': |
| 86 | raise ValueError(f"Card {i} has invalid suit: '{suit}'. Valid suits: S, H, D, C") |
| 87 | |
| 88 | # Parse card value |
| 89 | if value_str == 'A': |
| 90 | value = 14 |
| 91 | elif value_str == 'K': |
| 92 | value = 13 |
| 93 | elif value_str == 'Q': |
| 94 | value = 12 |
| 95 | elif value_str == 'J': |
| 96 | value = 11 |
| 97 | else: |
| 98 | try: |
| 99 | value = int(value_str) |
| 100 | if value < 2 or value > 10: |
| 101 | raise ValueError(f"Card {i} has invalid numeric value: {value}. Valid range: 2-10") |
| 102 | except ValueError as e: |
| 103 | if "invalid numeric value" in str(e): |
| 104 | raise e |
| 105 | raise ValueError(f"Card {i} has invalid value: '{value_str}'. Valid values: 2-10, J, Q, K, A") |
| 106 | |
| 107 | parsed_cards.append((value, suit)) |
| 108 | |
| 109 | # Validate hand size |
| 110 | if len(parsed_cards) != 5: |
| 111 | raise ValueError(f"Poker hand must contain exactly 5 cards, got {len(parsed_cards)}: {hand_str}") |
| 112 | |
| 113 | return parsed_cards |
| 114 | |
| 115 | |
| 116 | def rank_hand(hand: list[tuple]) -> tuple: |
| 117 | """ |
| 118 | Rank a poker hand and return a tuple for comparison. |
| 119 | |
| 120 | This function implements standard poker hand rankings according to Wikipedia: |
| 121 | https://en.wikipedia.org/wiki/List_of_poker_hands |
| 122 | |
| 123 | Hand rankings (highest to lowest): |
| 124 | 8: Straight Flush (including Royal Flush) |
| 125 | 7: Four of a Kind |
| 126 | 6: Full House |
| 127 | 5: Flush |
| 128 | 4: Straight |
| 129 | 3: Three of a Kind |
| 130 | 2: Two Pair |
| 131 | 1: One Pair |
| 132 | 0: High Card |
| 133 | |
| 134 | Args: |
| 135 | hand: List of tuples [(value, suit), ...] where value is 2-14 and suit is S/H/D/C |
| 136 | |
| 137 | Returns: |
| 138 | Tuple (rank, ...) where rank indicates the hand type and remaining elements |
| 139 | are used for tie-breaking within the same hand type. |
| 140 | |
| 141 | Return formats by hand type: |
| 142 | - Straight Flush: (8, high_card_value) |
| 143 | - Four of a Kind: (7, quad_value, kicker_value) |
| 144 | - Full House: (6, three_kind_value, pair_value) |
| 145 | - Flush: (5, [all_card_values_sorted_descending]) |
| 146 | - Straight: (4, high_card_value) |
| 147 | - Three of a Kind: (3, triplet_value, highest_kicker, second_kicker) |
| 148 | - Two Pair: (2, high_pair_value, low_pair_value, kicker_value) |
| 149 | - One Pair: (1, pair_value, highest_kicker, second_kicker, third_kicker) |
| 150 | - High Card: (0, [all_card_values_sorted_descending]) |
| 151 | |
| 152 | Examples: |
| 153 | >>> rank_hand([(14, 'S'), (13, 'S'), (12, 'S'), (11, 'S'), (10, 'S')]) # Royal Flush |
| 154 | (8, 14) |
| 155 | |
| 156 | >>> rank_hand([(7, 'S'), (7, 'H'), (7, 'D'), (7, 'C'), (2, 'S')]) # Four of a Kind |
| 157 | (7, 7, 2) |
| 158 | |
| 159 | >>> rank_hand([(5, 'S'), (4, 'H'), (3, 'D'), (2, 'C'), (14, 'S')]) # Ace-low Straight |
| 160 | (4, 5) |
| 161 | """ |
| 162 | # Extract and sort card values in descending order |
| 163 | values = sorted([card[0] for card in hand], reverse=True) |
| 164 | suits = [card[1] for card in hand] |
| 165 | |
| 166 | # Check for flush (all cards same suit) |
| 167 | is_flush = len(set(suits)) == 1 |
| 168 | |
| 169 | # Check for straight |
| 170 | is_straight = False |
| 171 | # Special case: Ace can be low (A,2,3,4,5) - the "wheel" or "bicycle" straight |
| 172 | if values == [14, 5, 4, 3, 2]: |
| 173 | is_straight = True |
| 174 | values = [5, 4, 3, 2, 1] # Renormalize Ace as 1 for consistent tie-breaking |
| 175 | elif all(values[i] - values[i+1] == 1 for i in range(4)): |
| 176 | is_straight = True |
| 177 | |
| 178 | # Count occurrences of each card value |
| 179 | value_counts = {} |
| 180 | for value in values: |
| 181 | value_counts[value] = value_counts.get(value, 0) + 1 |
| 182 | |
| 183 | # Sort counts and unique values for pattern matching |
| 184 | counts = sorted(value_counts.values(), reverse=True) |
| 185 | unique_values = sorted(value_counts.keys(), reverse=True) |
| 186 | |
| 187 | # Determine hand rank using standard poker hierarchy |
| 188 | if is_straight and is_flush: |
| 189 | # Straight flush (including royal flush) |
| 190 | return (8, values[0]) # High card value for tie-breaking |
| 191 | elif counts[0] == 4: |
| 192 | # Four of a kind |
| 193 | four_kind_value = [v for v, c in value_counts.items() if c == 4][0] |
| 194 | kicker = [v for v, c in value_counts.items() if c == 1][0] |
| 195 | return (7, four_kind_value, kicker) |
| 196 | elif counts[0] == 3 and counts[1] == 2: |
| 197 | # Full house |
| 198 | three_kind_value = [v for v, c in value_counts.items() if c == 3][0] |
| 199 | pair_value = [v for v, c in value_counts.items() if c == 2][0] |
| 200 | return (6, three_kind_value, pair_value) |
| 201 | elif is_flush: |
| 202 | # Flush |
| 203 | return (5, values) # All card values for tie-breaking |
| 204 | elif is_straight: |
| 205 | # Straight |
| 206 | return (4, values[0]) # High card value |
| 207 | elif counts[0] == 3: |
| 208 | # Three of a kind |
| 209 | three_kind_value = [v for v, c in value_counts.items() if c == 3][0] |
| 210 | kickers = sorted([v for v, c in value_counts.items() if c == 1], reverse=True) |
| 211 | return (3, three_kind_value, kickers[0], kickers[1]) |
| 212 | elif counts[0] == 2 and counts[1] == 2: |
| 213 | # Two pair |
| 214 | pairs = sorted([v for v, c in value_counts.items() if c == 2], reverse=True) |
| 215 | kicker = [v for v, c in value_counts.items() if c == 1][0] |
| 216 | return (2, pairs[0], pairs[1], kicker) |
| 217 | elif counts[0] == 2: |
| 218 | # One pair |
| 219 | pair_value = [v for v, c in value_counts.items() if c == 2][0] |
| 220 | kickers = sorted([v for v, c in value_counts.items() if c == 1], reverse=True) |
| 221 | return (1, pair_value, kickers[0], kickers[1], kickers[2]) |
| 222 | else: |
| 223 | # High card |
| 224 | return (0, values) # All card values for tie-breaking |
| 225 | |
| 226 | # Enhanced Features: |
| 227 | # - Comprehensive documentation with detailed docstrings and examples |
| 228 | # - Improved error messages with specific card position information |
| 229 | # - Better input validation with case-insensitive card parsing |
| 230 | # - Clear explanation of poker hand ranking algorithm |
| 231 | # - Type hints for better code maintainability |
| 232 | # |
| 233 | # Handled Edge Cases: |
| 234 | # - Empty hands list, single hand, empty/whitespace-only hand string |
| 235 | # - Invalid card format, invalid suit, invalid numeric value |
| 236 | # - Non-numeric values that aren't face cards, hands with incorrect number of cards |
| 237 | # - Ace-low straight (A-2-3-4-5) special case handling |
| 238 | # - Royal flush recognition as highest straight flush |
| 239 | # - Proper tie-breaking for all hand types |
| 240 | # |
| 241 | # Poker Hand Rankings (highest to lowest): |
| 242 | # 8: Straight Flush (including Royal Flush: A-K-Q-J-10 of same suit) |
| 243 | # 7: Four of a Kind |
| 244 | # 6: Full House |
| 245 | # 5: Flush |
| 246 | # 4: Straight |
| 247 | # 3: Three of a Kind |
| 248 | # 2: Two Pair |
| 249 | # 1: One Pair |
| 250 | # 0: High Card |