6
\$\begingroup\$

Write an improved code to identify a valid straight of eight cards, e.g. 4H,5S,AC,7C,8H,AH,0S,JC. Note that the card combination may include Wilds (as we see in our example, with the Ace of Clubs standing in for a Six and the Ace of Hearts standing in for a Nine), but must include at least two "natural" cards (i.e. non-Wild cards). Note also that the sequence of the cards is significant for this group type, and that 4H,5S,AC,8C,7H,AH,AS,AC, e.g., is not a valid straight of eight, as it is not in sequence. Aces are the only wild cards and must represent another card. Duplicate cards are allowed.

Card inputs are in a list of 2 value strings with the value:

  • 2 to 9 - for 2 to 9
  • 0 - for 10
  • J - for Jack
  • Q - for Queen
  • K - for King
  • A - for Ace

The suits are shown with values S(spade), C(club), H(heart), D(diamond)

  1. Example input to be checked: 4H,5S,AC,7C,8H,AH,0S,JC
    Output: True
  2. Example input to be checked: AH,5S,AC,7C,AH,AH,AS,AC
    Output: True
  3. Example input to be checked: 4H,5S,AC,8C,7H,AH,AS,AC
    Output: False

Here is my code:

group = input().split(",")
if len(group) == 8:
    #check for ace
    ace = [0 for card in group if card[0] == 'A']
    run_vals = {"0": 10, "J": 11, "Q": 12, "K":13}
    if len(ace) < 6:
        miss_count = 0
        for index in range(len(group)-1):
            if group[index][0]  == "A":
                miss_count += 1
                if group[index+1][0]  == "A" and index == len(group)-2:
                    miss_count += 1
                    continue
                else:
                    continue
                
            if group[index+1][0]  == "A" and index == len(group)-2:
               miss_count += 1
               continue
            if group[index+1][0]  == "A" and index <len(group)-2:
               continue
            
            if group[index+1][0] in run_vals:
                num1 = run_vals[group[index+1][0]]
            else:
                num1 = int(group[index+1][0])
                
            if group[index][0] in run_vals:
                num2 = run_vals[group[index][0]]
            else:
                num2 = int(group[index][0])

            if (num1 - num2) != 1:
                miss_count += 1
              
        if miss_count == len(ace):
            print(True)
        else:
            print(False)

    elif len(ace) == 6:
        ace_in_between = 0
        range_limits = [card for card in group if card[0] != "A"]
        for index in range(len(group)):
            if index > group.index(range_limits[0]) and index< group.index(range_limits[1]):
                ace_in_between += 1
        range_limits = [run_vals[val[0]] if val[0] in run_vals else val[0] for val in range_limits]
        if ace_in_between + 1 == (int(range_limits[1]) - int(range_limits[0]) ):
            print(True)
        else:
            print(False)
    else:
        print(False)
else:
    print(False)
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Could you please clarify what you are looking? Please read How to Ask. \$\endgroup\$
    – mangupt
    May 21 at 16:07
  • \$\begingroup\$ Can an ace represent a “low ace”? How about a “high ace”? As in, is “ace, 2, 3 … 8” a valid straight? How about “7, 8, … king, ace”? \$\endgroup\$
    – AJNeufeld
    May 22 at 15:40
  • \$\begingroup\$ ace only represents other cards, not an ace itself. The highest possible straight card is the King of any suit and the lowest possible straight card is the 2 of any suit. \$\endgroup\$ May 22 at 15:55
  • 1
    \$\begingroup\$ Please don't vandalize your posts like that. I've rolled back your edit. \$\endgroup\$
    – Mast
    May 25 at 7:55
6
\$\begingroup\$

Good news! There's lots of room for improvement in your code! (How's that for a positive spin?)

Counting Aces

    ace = [0 for card in group if card[0] == 'A']

This code creates a list (ace), which only contains 0 elements equal in number to the number of aces in group. The contents of the list is never used, only the length. Finally, the length of the list is queried twice.

That is a very heavy implementation for a simple count of the number of aces:

    aces = sum(card[0] == 'A' for card in group)

Or slightly less obliquely:

    aces = sum(1 for card in group if card[0] == 'A')

Decoding Cards

    run_vals = {"0": 10, "J": 11, "Q": 12, "K":13}
    ...
            if group[index+1][0] in run_vals:
                num1 = run_vals[group[index+1][0]]
            else:
                num1 = int(group[index+1][0])

            if group[index][0] in run_vals:
                num2 = run_vals[group[index][0]]
            else:
                num2 = int(group[index][0])
    ...
        range_limits = [run_vals[val[0]] if val[0] in run_vals else val[0] for val in range_limits]
        if ace_in_between + 1 == (int(range_limits[1]) - int(range_limits[0]) ):

I'm seeing a lot of in run_vals tests and int(...) casts. Imagine how much cleaner your code could look if you simply filled in all of the run_vals?

    run_vals = {"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}

I've added "A" mapping to 1 just for completeness. It would allow you to test all(card[0] in run_vals for card in group) to check that all the cards have a valid rank, such that nobody sneaks in a "1 of Spades" for example.

For that matter, you could check for valid suits too, such as all(card[1] in {'C', 'D', 'H', 'S'} for card in group), to avoid someone sneaking in the "7 of Pentacles".

A Straight

The problem would be dead simple if there was no wild cards. You could write:

    rank = [run_vals[card[0]] for card in group]
    start = rank[0]
    for idx in range(len(rank)):
        if rank[idx] != start + idx:
            return False
    return True

Supporting wild cards would be easy too, if we knew the first card was not an ace. We could just add an exception for the aces:

    for idx in range(len(rank)):
        if rank[idx] != start + idx and rank[idx] != 1:
            return False

Starting Value

What happens if the ace is the first card? Well, the second card (assume it was not an ace) would dictate the start was one rank lower. Or if that was an ace, then the third card would, and so on:

    for idx in range(len(rank)):
        if rank[idx] == 1:
            start = rank[idx] - idx
            break
    else:
        return False     # Only aces exist!

Functions!

Your code is one complex script without any functions. If you haven't learned how to write them yet, learn. If you have learned, use them!

Reworked Code

The following code is a reimplementation of some of the above ideas. However, the loops like for idx in len(range(list)) have been replaced with more Pythonic for idx, value in enumerate(list) loops. Loops which return a False if one iteration produces a False result have been replaced with an all(...) statement with a generator expression. It also demonstrates some type-hints, """docstrings""" (including the use of (doctest) to help write code which is more understandable to others, and contain built-in unit tests.

Another improvement is the problem statement states there must be at least 2 "natural" cards. Instead of testing for the number of aces is less than or equal to 6 -- a number which appears nowhere in the problem statement -- it directly tests against the given value 2.

RANK = dict(zip("A234567890JQK", range(1, 14)))
WILD = RANK['A'] 

def straight_of_eight(card_string: str) -> bool:
    """
    Determine if a string of cards is an ascending straight,
    with at least 2 natural cards.

    >>> straight_of_eight("4H,5S,AC,7C,8H,AH,0S,JC")
    True

    >>> straight_of_eight("4H,5S,AC,8H,7C,AH,0S,JC")
    False
    """
    
    ranks = [RANK[card[0]] for card in card_string.split(',')]

    if len(ranks) != 8:
        return False   # Were not given exactly 8 cards

    if sum(rank != WILD for rank in ranks) < 2:
        return False   # Not enough natural cards

    # Determine starting rank for the straight
    start = next(rank - idx for idx, rank in enumerate(ranks) if rank != WILD)

    return 2 <= start <= 6 and all(rank == idx or rank == WILD
                                   for idx, rank in enumerate(ranks, start))

if __name__ == '__main__':
    import doctest
    doctest.testmod()

    group = input()
    print(straight_of_eight(group))
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.