Skip to main content

SimplifiedPoker

/**
* Interview question: Simplified Poker
*
* Compare two 4-card hands and return which hand wins.
*
* A hand is represented as a string of four card ranks, for example:
* - "2332"
* - "9991"
* - "1234"
*
* Card ranks are digits from "1" to "9", where "9" is strongest.
*
* Hand strength, from strongest to weakest:
* 1. Four of a kind
* 2. Two pair
* 3. Three of a kind
* 4. One pair
* 5. High card
*
* Note: this is intentionally simplified and does not follow normal poker
* exactly. In this prompt, two pair beats three of a kind.
*
* Tie-breaking rule:
* If both hands have the same hand type, compare their original cards from
* right to left. Do not sort the cards for tie-breaking.
*
* Approach:
* - Count how many times each rank appears.
* - Convert those counts into a hand type.
* - Compare hand types first.
* - If hand types tie, compare original card order from right to left.
*
* Complexity:
* - Time: O(n), where n is the hand length. Here n is always 4.
* - Space: O(k), where k is the number of distinct ranks in a hand.
*/
const Result = {
HAND_1: "HAND_1",
HAND_2: "HAND_2",
TIE: "TIE",
};

const HandType = {
FOUR_OF_A_KIND: 5,
TWO_PAIR: 4,
THREE_OF_A_KIND: 3,
ONE_PAIR: 2,
HIGH_CARD: 1,
};

/**
* Rank values.
* Higher number means stronger card.
*/
const CARD_RANK = {
"9": 9,
"8": 8,
"7": 7,
"6": 6,
"5": 5,
"4": 4,
"3": 3,
"2": 2,
"1": 1,
};

/**
* Count cards in a hand.
*
* Example:
* "2332" -> Map { "2" => 2, "3" => 2 }
*
* This frequency map is the only information needed to classify the hand type.
*/
function getCardCounts(hand) {
const counts = new Map();

for (const card of hand) {
counts.set(card, (counts.get(card) || 0) + 1);
}

return counts;
}

/**
* Classify the hand type.
*
* Important:
* The problem ranks Two Pair above Three of a Kind,
* even though traditional poker does the opposite.
*/
function getHandType(hand) {
const counts = getCardCounts(hand);

// Sort frequencies descending so the largest group is first.
// Example:
// "9991" -> [3, 1]
// "2332" -> [2, 2]
const frequencies = Array.from(counts.values()).sort((a, b) => b - a);

if (frequencies[0] === 4) {
return HandType.FOUR_OF_A_KIND;
}

if (frequencies[0] === 2 && frequencies[1] === 2) {
return HandType.TWO_PAIR;
}

if (frequencies[0] === 3) {
return HandType.THREE_OF_A_KIND;
}

if (frequencies[0] === 2) {
return HandType.ONE_PAIR;
}

return HandType.HIGH_CARD;
}

/**
* Tie-break two hands with the same hand type.
*
* Rule:
* Compare original cards from right to left.
* Do not sort the hand.
*/
function compareByOriginalOrderRightToLeft(hand1, hand2) {
for (let i = hand1.length - 1; i >= 0; i--) {
const card1 = CARD_RANK[hand1[i]];
const card2 = CARD_RANK[hand2[i]];

if (card1 > card2) return Result.HAND_1;
if (card2 > card1) return Result.HAND_2;
}

return Result.TIE;
}

/**
* Main API.
*
* @param {string} hand1
* @param {string} hand2
* @returns {"HAND_1" | "HAND_2" | "TIE"}
*/
function evaluate(hand1, hand2) {
const type1 = getHandType(hand1);
const type2 = getHandType(hand2);

// Higher numeric HandType means a stronger hand category.
if (type1 > type2) {
return Result.HAND_1;
}

if (type2 > type1) {
return Result.HAND_2;
}

return compareByOriginalOrderRightToLeft(hand1, hand2);
}

/**
* Example input/output:
*
* evaluate("9999", "8888")
* Output: "HAND_1"
* Reason: both are four of a kind, then right-to-left tie-break picks 9 over 8.
*
* evaluate("2332", "9991")
* Output: "HAND_1"
* Reason: two pair beats three of a kind in this simplified ranking.
*
* evaluate("1234", "1235")
* Output: "HAND_2"
* Reason: both are high card; compare from right to left, 5 beats 4.
*
* evaluate("1223", "1223")
* Output: "TIE"
*/