A famous casino is suddenly faced with a sharp decline of their revenues. They decide to offer Texas hold'em also online. Can you help them by writing an algorithm that can rank poker hands? 

Task:

Create a poker hand that has a constructor that accepts a string containing 5 cards:

```python
hand = PokerHand("KS 2H 5C JD TD")
```

The characteristics of the string of cards are:

A space is used as card seperator
Each card consists of two characters
The first character is the value of the card, valid characters are:
`2, 3, 4, 5, 6, 7, 8, 9, T(en), J(ack), Q(ueen), K(ing), A(ce)`
The second character represents the suit, valid characters are: 
`S(pades), H(earts), D(iamonds), C(lubs)`



The poker hands must be sortable by rank, the highest rank first:

```python
hands = []
hands.append(PokerHand("KS 2H 5C JD TD"))
hands.append(PokerHand("2C 3C AC 4C 5C"))
hands.sort() (or sorted(hands))
```

Apply the Texas Hold'em rules for ranking the cards. 
There is no ranking for the suits.
An ace can either rank high or rank low in a straight or straight flush. Example of a straight with a low ace:
`"5C 4D 3C 2S AS"`




Note:  there are around 25000 random tests, so keep an eye on performances.

Difficulty: interview

Starter code:

def __repr__(self):
	

Solutions:
from functools import total_ordering

@total_ordering
class PokerHand(object):
    CARDS = "AKQJT987654321"
    RANKS = {card: idx for idx, card in enumerate(CARDS)}
    
    def score(self, hand):    
        values, suits = zip(*hand.split())
        idxs, ordered = zip(*sorted((self.RANKS[card], card) for card in values))
        is_straight = ''.join(ordered) in self.CARDS
        is_flush = len(set(suits)) == 1
        return (-2 * sum(values.count(card) for card in values)
                - 13 * is_straight - 15 * is_flush, idxs)
    
    def __init__(self, hand):
        self.hand = hand
        self.score = min(self.score(hand), self.score(hand.replace('A', '1')))
    
    def __repr__(self):  return self.hand
    def __eq__(self, other): return self.score == other.score
    def __lt__(self, other): return self.score < other.score

from collections import Counter

HEX_CARDS     = "23456789ABCDE"
CARD_TO_HEX   = str.maketrans("TJQKA", "ABCDE")

class PokerHand(object):

    def __init__(self, hand):
        self.hand    = hand
        hand         = hand.translate(CARD_TO_HEX)
        valCount     = Counter( hand[n] for n in range(0, len(hand), 3) )
        colorCount   = Counter( hand[n] for n in range(1, len(hand), 3) )
        
        minCardIdx   = int(min(valCount.keys()), 16) - 2
        isStraight   = list(valCount.keys()) == set(HEX_CARDS[minCardIdx:minCardIdx+5])
        
        if list(valCount.keys()) == {'2','3','4','5','E'}:
            isStraight = True
            valCount['1'] = valCount.pop('E')
        
        mainVal = next( i for i,elt in enumerate([5 in list(colorCount.values()) and isStraight,              # Straight Flush
                                                  4 in list(valCount.values()),                               # Square
                                                  3 in list(valCount.values()) and 2 in list(valCount.values()),    # Full house
                                                  5 in list(colorCount.values()),                             # Flush
                                                  isStraight,                                           # Straight
                                                  3 in list(valCount.values()),                               # Three of a kind
                                                  2 in list(valCount.values()) and len(list(valCount.keys())) == 3, # 2 pairs
                                                  2 in list(valCount.values()) and len(list(valCount.keys())) == 4, # 1 pair,
                                                  True]) if elt )                                       # 1 card
                                                  
        self.handValue = int(''.join(sorted(list(valCount.keys()), key = lambda k: (valCount[k], k), reverse = True)), 16) + (8 - mainVal) * 16**5 
                
    def __eq__(self, other):  return self.handValue == other.handValue
    def __lt__(self, other):  return self.handValue >  other.handValue
    def __gt__(self, other):  return self.handValue <  other.handValue
    
    def __repr__(self):       return "PokerHand({})".format(self.hand)


class PokerHand(object):
    ''' The class represent a poker hand at Texas Holdem Poker.
        A hand consists of 5 best cards from 7 cards(2-pocket, 5-table).
        And this 5 cards is a combination, each combination have a value.
        The hand with a larger value - wins.
        
        The class contains following functions:
            * hand_to_vector - converts a hand in vector of count cards
            * combination - returns the name of the combination of a hand
            * value - returns the value of the combination (can be compared with another hand)
            * compare_with - compares the instance hand with other hand
    '''
    
    def __init__(self, hand):
        self.hand = hand
        self.hand_value = self.value(hand)
    
    
    def __repr__(self):  return self.hand
  
    def __lt__(self, other):  return self.hand_value > other.hand_value
    def __eq__(self, other): return self.hand_value == other.hand_value
    
    def compare_with(self, other):
        ''' Compares 2 hands: first - instance hand, second - param
            Args:
                other (PokerHand): Another instance of PokerHand
            Returns:
                str: the result of the game if our and another hand were played
        '''
        if self.hand_value > other.hand_value:
            return 'Win'
        elif self.hand_value < other.hand_value:
            return 'Loss'
        elif self.hand_value == other.hand_value:
            return 'Tie'
  
  
    @staticmethod
    def hand_to_vector(hand):
        ''' Parse a hand in a vector, where a value of the vector is count of cards
            card:     2  3  4      K  A
            vector:  [0, 0, 0, ... 0, 0]    
        Args:
            hand (str): A combination of five cards, example '2S AH 4H 5S 6C'
        Returns:
            list: A vector of count of cards
        '''
        vec_hand = [0]*13
        for card in hand.split():
            try:
                vec_hand[int(card[0])-2] += 1
            except:
                switcher = {'A': 12, 'K' : 11, 'Q' : 10, 'J' : 9, 'T' : 8}
                vec_hand[switcher[card[0]]] += 1
        return vec_hand
      
      
    @staticmethod
    def combination(hand):
        ''' Firuers out the combination of the hand
        Args:
            hand (str): A combination of five cards, example '2S AH 4H 5S 6C'
        Returns:
            str: the name of the combination
        '''
        vec_hand = PokerHand.hand_to_vector(hand)
        # suited --> True, when all cards of the same suit
        suited = False
        if 1 == len(set([x[1] for x in hand.split()])):
            suited = True
        
        if 4 in vec_hand:
            return 'Four of a kind'
        else:
            if 3 in vec_hand:
                if 2 in vec_hand:
                    return 'Full house'
                else:
                    return 'Three of a kind'
            else:
                if 2 in vec_hand:
                    if 2 == vec_hand.count(2):
                        return 'Two pair'
                    else:
                        return 'One pair'
                if '11111' not in ''.join([str(x) for x in (vec_hand[-1:]+vec_hand)]):
                    if suited:
                        return 'Flush'
                    else:
                        return 'High card'
                else:
                    if not suited:
                        return 'Straight'
                    else:
                        if vec_hand[8] == 1 and vec_hand[12] == 1:
                            return 'Royal flush'
                        else:
                            return 'Straight flush'
                        
        
    @staticmethod
    def value(hand):
        ''' Calculates the value of the hand
        Args:
            hand (str): A combination of five cards, example '2S AH 4H 5S 6C'
        Returns:
            int: the value of the hand
        '''
        hand_vec = PokerHand.hand_to_vector(hand)
        ''' value_vec - a splited binary number, where each section represents
            a specific combination
            [ Straight flush, Care, Full house(set), Full house(pair), Flush, Straight,
              Two pair(Hihg pair), Two pair(Low pair), One pair ]
        '''
        value_vec = ['0','0000','0000','0000','0','0','0000','0000','0000','0000']
        combination = PokerHand.combination(hand)
        if combination == 'One pair':
            value_vec[9] = '{:04b}'.format(hand_vec.index(2))
        elif combination == 'Two pair':
            value_vec[8] = '{:04b}'.format(hand_vec.index(2))
            value_vec[7] = '{:04b}'.format(hand_vec.index(2, hand_vec.index(2)+1))
        elif combination == 'Three of a kind':
            value_vec[6] = '{:04b}'.format(hand_vec.index(3))
        elif combination == 'Straight':
            value_vec[5] = '1'
            if hand_vec[12] == 1 and hand_vec[3] == 1:
                hand_vec[12] = 0
        elif combination == 'Flush':
            value_vec[4] = '1'
        elif combination == 'Full house':
            value_vec[3] = '{:04b}'.format(hand_vec.index(2))
            value_vec[2] = '{:04b}'.format(hand_vec.index(3))
        elif combination == 'Four of a kind':
            value_vec[1] = '{:04b}'.format(hand_vec.index(4))
        elif combination == 'Straight flush':
            value_vec[0] = '1'
            if hand_vec[12] == 1 and hand_vec[3] == 1:
                hand_vec[12] = 0
        elif combination == 'Royal flush':
            value_vec[0] = '1'
            value_vec[1] = '1111'

        ''' The result is binary number: value_vec extended 
            with hand_vec(contains only 0/1, to represent remaining single cards),
            and this resulting extended binary number convert to decimal.
        '''
        return int(''.join(value_vec) + ''.join(reversed(list([str(x) if x == 1 else str(0) for x in hand_vec]))), 2)



class PokerHand(object):
    CARD = "23456789TJQKA"
    def __repr__(self): return str(self.hand)
    def __init__(self, hand):
        self.hand = hand
        values = ''.join(sorted(hand[::3], key=self.CARD.index))
        suits = set(hand[1::3])
        is_straight = values in self.CARD or values == "2345A"
        is_flush = len(suits) == 1
        self.score = (2 * sum(values.count(card) for card in values)
                      + 13 * is_straight + 15 * is_flush,
                      [self.CARD.index(card) for card in values[::-1]])
        if(values == "2345A"):      
            self.score = (13 * is_straight + 15 * is_flush + 10, [3, 2, 1, 0, 12])
    def __lt__(self, other): return (self.score > other.score)

class PokerHand(object):
    CARD = "23456789TJQKA"
    RESULT = ["Loss", "Tie", "Win"]

    def __repr__(self):  return self.hand
    
    def __lt__(self, other):
        res = self.compare_with(other)        
        return res == self.RESULT[2]   

    def __init__(self, hand):
        self.hand = hand
        values = ''.join(sorted(hand[::3], key=self.CARD.index))        
        suits = set(hand[1::3])
        is_straight = values in self.CARD
        is_straight_bad = values in ['2JQKA','23QKA','234KA','2345A']        
        is_flush = len(suits) == 1
        self.score = (2 * sum(values.count(card) for card in values)
                      + 13 * is_straight + (12.5 * is_straight_bad) + 15 * is_flush,
                      [self.CARD.index(card) for card in values[::-1]])
        
    def compare_with(self, other):        
        return self.RESULT[(self.score > other.score) - (self.score < other.score) + 1]

from collections import defaultdict
from typing import List


class PokerHand(object):
    '''Representation of a poker hand and its basic characteristics.

    The PokerHands Weight are:
        Straight Flush  : 1
        Four of a Kind  : 2
        Full House      : 3
        Flush           : 4
        Straight        : 5
        Three of a Kind : 6
        Two Pairs       : 7
        Pair            : 8
        High Card       : 9
    
    In the sample test, the sorted poker hands are displayed with the highest ranking hand in the top.
    The sort method, sorts in ascending order, this means the highest weight would have to be considered the lowest number.

    If there are ties between the hands weight, the tiebreaker shall be resolved as follow:
        Straight Flush or Straight:
            The hand with the highest card in the Straight.
            There's no need to account for the Royal Straight Flush because of this.
        Four of a Kind:
            The highest Four of a Kind, then the Kicker.
        Full House:
            The highest Three of a Kind, then the highest Pair.
        Flush:
            The hand with the highest card in the Flush wins.
        Three of a Kind:
            The highest Three of a Kind, then the highest Kicker.
        Two Pairs or Pair:
            The highest Pair, then the highest Kicker.
        High Card:
            The highest card in the hand.

    If still there is a tie, then it is a tie because all the Suits have the same weight.

    Attributes:
        hand (str): String representation of the poker hand.
        cards (List[PokerCard]): The list of poker cards in this hand.
    '''
    def __repr__(self):
        return self.hand

    def __init__(self, hand: str):
        self.hand = hand
        self.cards = [PokerCard(card) for card in hand.split(' ')]
        self.cards.sort()
        self.hand_weight = self._calculate_hand_weight()
        
    def __eq__(self, other):
        if self.hand_weight[0] != other.hand_weight[0]:
            return False
        
        for i in range(1, len(self.hand_weight)):
            if self.hand_weight[i] != other.hand_weight[i]:
                return False
        return True
    
    def __lt__(self, other):
        if self.hand_weight[0] < other.hand_weight[0]:
            return True
        
        # if both hands have teh same weight, the tie must be broken by highest ranking cards.
        if self.hand_weight[0] == other.hand_weight[0]:
            for i in range(1, len(self.hand_weight)):
                # if the rank is also equal, you want to check the next cards, if any are left
                if self.hand_weight[i] == other.hand_weight[i]:
                    continue
                if self.hand_weight[i] < other.hand_weight[i]:
                    return True
                return False
        # if it is a tie
        return False

    def _calculate_hand_weight(self):
        '''Analize hand strenght.

        Returns:
            A Tuple containing the hand weight as its firs argument,
            then the cards ranks to break a tie in case the hand weight is the same.
        '''
        hand = defaultdict(int)
        for card in self.cards:
            hand[card.rank] += 1

        length = len(hand)
        # values holds the count for how many times each rank appears in the hand
        values = list(hand.values())

        # five different cards
        if length == 5:
            suited = self.is_suited()
            straight_high_card = self.is_a_straight()

            # if it is a straight flush:
            if suited and straight_high_card:
                return (1, straight_high_card)
            # if it is a flush:
            if suited:
                return (4, *self.cards)
            # if it is a straight:
            if straight_high_card:
                return (5, straight_high_card)
            # high card:
            return (9, *self.cards)

        if length == 2:
            # four of a kind:
            if 4 in values:
                i_four = values.index(4)
                i_kicker = values.index(1)
                return (2, self.cards[i_four], self.cards[i_kicker])

            # full house:
            i_triple = values.index(3)
            i_pair = values.index(2)
            return (3, self.cards[i_triple], self.cards[i_pair])

        if length == 3:
            # triple:
            if 3 in values:
                i_triple = values.index(3)
                return (6, self.cards[i_triple], *self.cards[:i_triple], *self.cards[i_triple+1:])

            # two pairs:
            i_pair = values.index(2)
            j_pair = values.index(2, i_pair+1)
            i_kicker = values.index(1)
            return (7, self.cards[i_pair], self.cards[j_pair], self.cards[i_kicker])

        if length == 4:
            # this is a pair:
            i_pair = values.index(2)
            return (8, self.cards[i_pair], *self.cards[:i_pair], *self.cards[i_pair+1:])

    def is_suited(self):
        '''Check if hand is Suited.'''
        for i in range(len(self.cards)-1):
            if self.cards[i].suit != self.cards[i+1].suit:
                return False

        return True
    
    def is_a_straight(self):
        '''Check if hand is a Straight
        
        Returns:
            The highest card in the Straight.
        '''
        starter = 0
        # if True, skips the first element to check for the Low Straight
        if self.cards[0].rank == 'A' and self.cards[1].rank == '5':
            starter = 1

        for i in range(starter, len(self.cards)-1):
            if self.cards[i+1] - self.cards[i] != 1:
                return False

        # in case it was a Low Straight, you want to return the 5 instead of the Ace
        if starter == 1:
            return self.cards[1]

        return self.cards[0]


class PokerCard(object):
    '''Representation of a poker card and its basic characteristics.

    The PokerCards Weight are:
        Ace:    1       |   7:      8
        King:   2       |   6:      9
        Queen:  3       |   5:      10
        Jack:   4       |   4:      11
        Ten:    5       |   3:      12
        9:      6       |   2:      13
        8:      7       |   Ace*:   When comparing it in a low Straight

    Attributes:
        card (str): String representation of the poker card.
        rank (str): The rank of the card.
        suit (str): The suit of the card.
    '''
    def __repr__(self):
        return self.card_weight
    
    def __init__(self, card: str):
        self.card = card
        self.rank = card[0]
        self.suit = card[1]
        self.card_weight = self.get_card_weight()
    
    def __eq__(self, other):
        return True if self.card_weight == other.card_weight else False
    
    def __lt__(self, other):
        return True if self.card_weight < other.card_weight else False
    
    def __sub__(self, other):
        return self.card_weight - other.card_weight
    
    def get_card_weight(self):
        '''Return the card weight based on its rank.'''
        if self.rank == 'T':
            return 5
        if self.rank == 'J':
            return 4
        if self.rank == 'Q':
            return 3
        if self.rank == 'K':
            return 2
        if self.rank == 'A':
            return 1
        return (63 - ord(self.rank))


from itertools import groupby
from enum import IntEnum, auto

class PokerHand:
    def __init__(self, hand):
        self.hand = hand
        cards = hand.split()
        values = tuple(sorted(cardval.get(c[0]) or int(c[0]) for c in cards))
        groups = tuple(sorted((len(tuple(g)) for _, g in groupby(values)), reverse=True))
        hand_value = hv_table.get(groups)
        if hand_value is None:
            flush = len(set(c[1] for c in cards)) == 1
            straight = set(a-b for (a, b) in zip(values[1:], values[:-1])) == {1} or values == (2,3,4,5,14)
            if straight and flush:
                hand_value = HV.royal_flush if values[0] == 10 else HV.straight_flush
            elif flush:
                hand_value = HV.flush
            elif straight:
                hand_value = HV.straight
            else:
                hand_value = HV.highcard
        self.hand_value = hand_value
    
    def __lt__(lhs, rhs):
        return lhs.hand_value > rhs.hand_value

    def __eq__(lhs, rhs):
        return lhs.hand_value == rhs.hand_value

    def __repr__(self):
        return self.hand
    


cardval = dict(T=10, J=11, Q=12, K=13, A=14)

class HV(IntEnum):
    highcard = auto()
    pair = auto()
    two_pairs = auto()
    three_ok_a_kind = auto()
    straight = auto()
    flush = auto()
    full_house = auto()
    four_of_a_kind = auto()
    straight_flush = auto()
    royal_flush = auto()

hv_table = {
    (2,1,1,1): HV.pair,
    (2,2,1): HV.two_pairs,
    (3,1,1): HV.three_ok_a_kind,
    (3,2): HV.full_house,
    (4,1): HV.four_of_a_kind,
}


from typing import Tuple, List
from functools import total_ordering


@total_ordering
class PokerHand(object):
    """Create an object representing a Poker Hand based on an input of a
    string which represents the best 5 card combination from the player's hand
    and board cards.

    Attributes:
        hand: string representating the hand consisting of five cards
        cards: list of two tuple containing the value of the card and suit
            in sorted order
        hand value: total hand value of all the cards
        hand type: hand type as a number from the list given in docstring of
            hand_type function
        high card: value of the high card in a given hand

    Methods:
        compare_with(self, opponent): takes in player's hand (self) and
            opponent's hand (opponent) and compares both hands according to
            rules of Texas Hold'em.
            Returns one of 3 strings (Win, Loss, Tie) based on whether
            player's hand is better than opponent's hand.

    Helper Methods:
        _hand_with_values: Internal representation of hand as a list of two
            tuple containing the value of the card and suit in sorted order
        _is_flush: Determine whether a hand is flush or not
        _is_straight: Determine whether a hand is straight or not
        _is_same_kind: Determine whether a hand is of any kind (Look at doc
            for complete list)
        _hand_type: represent hand type as a number in order of precedence
        _high_card: Value of the highest card to break the tie in straight or
            straight flush
        _total_hand_value: Total value of the hand
        _compare_cards: Compare each cards from self and other
        _tie_breaker: To determine the outcome if self and other are of same
            type
    """
    def __init__(self, hand: str):
        self._hand = hand
        self._cards = self._hand_with_values()
        self._hand_value = self._total_hand_value()
        self._hand_type = self._hand_type()
        self._high_card = self._high_card()
        self._first_pair = 0
        self._second_pair = 0

    def compare_with(self, other: 'PokerHand') -> str:
        """
        Determines the outcome of the comparing self hand with other hand.
        Returns the output either 'Win', 'Loss', 'Tie' accordingly.
        :type other: PokerHand
        """
        # Basic comparison
        if self._hand_type > other._hand_type:
            return 'Win'
        elif self._hand_type < other._hand_type:
            return 'Loss'
        else:
            return self._tie_breaker(other, self._hand_type)

    def _tie_breaker(self, other: 'PokerHand', hand_type: int) -> str:
        """
        This method is used to break the tie if self hand and other hand
        has same type.
        For royal flush, it can only be a Tie.
        For straigh flush or straight compare the high card.
        For four of a kind or full house compare the first pair and if they
            are same then compare the hand value.
        For three of a kind or one pair, compare the first pair and if they
            are same then compare all the cards in reverse order as the cards
            are sorted.
        For two pairs, compare the first pair and then the second pair and if
            both are the same then compare the hand value.
        For flush and high card, compare all the cards in reverse order.
        :type other: PokerHand
        """
        if hand_type == 23:  # Royal flush
            return 'Tie'

        elif hand_type in [22, 18]:  # Straight flush or Straight
            return 'Win' if self._high_card > other._high_card else 'Loss' \
                if self._high_card < other._high_card else 'Tie'

        elif hand_type in [21, 20]:  # Four of a kind or Full house
            if self._first_pair == other._first_pair:
                return 'Win' if self._hand_value > other._hand_value else \
                    'Loss' if self._hand_value < other._hand_value else 'Tie'
            return 'Win' if self._first_pair > other._first_pair else 'Loss'

        elif hand_type in [17, 15]:  # Three of a kind or One pair
            if self._first_pair == other._first_pair:
                return self._compare_cards(other)
            return 'Win' if self._first_pair > other._first_pair else 'Loss'

        elif hand_type == 16:  # Two pairs
            if self._first_pair == other._first_pair:
                if self._second_pair == other._second_pair:
                    return 'Win' if self._hand_value > other._hand_value else \
                     'Loss' if self._hand_value < other._hand_value else 'Tie'
                else:
                    return 'Win' if self._second_pair > other._second_pair \
                        else 'Loss'
            return 'Win' if self._first_pair > other._first_pair else 'Loss'

        else:  # Flush and high card
            return self._compare_cards(other)

    def _compare_cards(self, other: 'PokerHand') -> str:
        """Comparing all the cards from its highest value to the lowest
        and accordingly returns either 'Win', 'Loss', 'Tie'.
        :type other: PokerHand
        """
        for i in range(4, -1, -1):
            if self._cards[i][0] != other._cards[i][0]:
                return 'Win' if self._cards[i][0] > other._cards[i][0] else \
                    'Loss'
        return 'Tie'

    def _hand_type(self) -> int:
        """
        Determine the type of hand.
        Returns the value from the following table:
        23: Royal flush
        22: Straight flush
        21: Four of a kind
        20: Full house
        19: Flush
        18: Straight
        17: Three of a kind
        16: Two pairs
        15: One pair
        14: Decide with highest card
        """
        if self._is_flush() and (self._is_five_high_straight() or
                                 self._is_straight()):
            if self._hand_value == 60:
                return 23
            else:
                return 22
        elif self._is_flush():
            return 19
        elif self._is_five_high_straight() or self._is_straight():
            return 18
        elif self._is_same_kind():
            kind_type = self._is_same_kind()
            return 14 + kind_type
        else:
            return 14

    def _high_card(self) -> int:
        """Return the value of highest card in a hand"""
        return self._cards[-1][0]

    def _total_hand_value(self) -> int:
        """Return the total hand value"""
        return sum(card[0] for card in self._cards)

    def _is_flush(self) -> bool:
        """
        Determine whether hand is a flush or not.
        Returns True or False
        """
        suit = self._cards[0][-1]
        return all(map(lambda card: suit in card, self._cards))

    def _is_five_high_straight(self) -> bool:
        """
        Determine whether a hand is a five high straight (low ace)
        If it is change the location of ace from the end of the list to the
        start otherwise don't do anything. If asked again then don't pop the
        last element again. Check whether the last element is ace or not.
        Returns True or False
        """
        if self._hand_value == 28:
            if self._cards[-1][0] == 14:
                ace_card = self._cards.pop()
                self._cards.insert(0, ace_card)
            return True
        return False

    def _is_straight(self) -> bool:
        """
        Determine whether hand is straight or not.
        Returns True or False.
        """
        for i in range(4):
            if self._cards[i+1][0] - self._cards[i][0] != 1:
                return False
        return True

    def _is_same_kind(self) -> int:
        """
        Determine the kind of hand if it is any and assign the value of the
        first and second pair to the respective attributes.
        Returns kind value else False.
        Kind Values:
        7: Four of a kind
        6: Full house
        3: Three of a kind
        2: Two pairs
        1: One pair
        0: False
        """
        kind = val1 = val2 = 0
        for i in range(4):
            if self._cards[i][0] == self._cards[i+1][0]:
                if not val1:
                    val1 = self._cards[i][0]
                    kind += 1
                elif val1 == self._cards[i][0]:
                    kind += 2
                elif not val2:
                    val2 = self._cards[i][0]
                    kind += 1
                elif val2 == self._cards[i][0]:
                    kind += 2
        kind = kind + 2 if kind in [4, 5] else kind
        self._first_pair = max(val1, val2)
        self._second_pair = min(val1, val2)
        return kind

    def _hand_with_values(self) -> List[Tuple[int, str]]:
        """
        Subsitute word values with int values and convert the hand into
        2-tuple lists having the values (value, suit).
        Returns the sorted hand with the above changes.
        """
        trans = {
            'T': '10',
            'J': '11',
            'Q': '12',
            'K': '13',
            'A': '14'
        }
        new_hand = self._hand.translate(str.maketrans(trans)).split()
        final_hand = [(int(card[:-1]), card[-1]) for card in new_hand]
        return sorted(final_hand)

    def __repr__(self):
        return self._hand

    def __str__(self):
        return self._hand

    def __lt__(self, other: 'PokerHand'):
        outcome = self.compare_with(other)
        return True if outcome == 'Win' else False

    def __eq__(self, other: 'PokerHand'):
        outcome = self.compare_with(other)
        return True if outcome == 'Tie' else False

from collections import Counter
class PokerHand(object):
    CARD = "123456789TJQKA"

    def __repr__(self):  return self.hand

    def __init__(self, hand):
        self.hand = hand
        CARD = self.CARD
        values = ''.join(sorted(hand[::3], key=CARD.index))
        if all([c in values for c in 'A2345']):
            CARD = 'A' + self.CARD[1:-1]
            values = ''.join(sorted(hand[::3], key=CARD.index))
        is_flush = len(set(hand[1::3])) == 1
        counts = Counter(values)
        is_straight = values in CARD
        self.score = (2 * sum(counts[card] for card in values)
                      + 13 * is_straight + 15 * is_flush,
                      sorted((cnt, CARD.index(card)) for card, cnt in counts.most_common())[::-1])
    def __lt__(self, other):
        return self.score > other.score


class PokerHand(object):
    VALUES = "A23456789TJQKA"
    

    def __repr__(self):  return self.hand

    def __init__(self, hand):
        self.hand = hand
        values = ''.join(sorted(hand[::3], key=self.VALUES[1:].index))
        suits = set(hand[1::3])
        hi_straight = values in self.VALUES
        lo_straight = values[-1] + values[:-1] in self.VALUES
        is_flush = len(suits) == 1
        self.score = (2 * sum(values.count(card) for card in values)
                      + 13 * (hi_straight or lo_straight) + 15 * is_flush,
                     [self.VALUES[:-1].index(card) if lo_straight else self.VALUES[1:].index(card) for card in values[::-1]])
        
    def __lt__(self, other):
        return other.score < self.score