Implement logger and card ranking
Company: Rippling
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
This coding round contains two independent implementation tasks.
### Task A: Implement a configurable logger
Implement a `Logger` component that accepts text messages and supports the following independently configurable behaviors:
1. Remove all occurrences of a configured substring before output.
2. Truncate the message to at most a configured maximum number of characters before output.
3. Convert the entire message to uppercase before output.
4. Store messages in an internal list without printing them.
Requirements:
- Design a clear API for enabling, disabling, and configuring these behaviors.
- When multiple transformations are enabled, define and document the order in which they are applied.
- Define behavior for edge cases such as an empty removal string, a negative truncation length, `null` or empty input, and repeated calls.
- The stored value should be the transformed message unless you explicitly document another choice.
- Add a `search(query)` feature for stored messages. It should return all stored messages containing the query string. Discuss whether search is case-sensitive, the expected time complexity, and how you would optimize it if the number of stored messages became large.
### Task B: Rank custom five-card hands
Implement a program that ranks five-card hands in a simplified card game.
Input:
- A list of records. Each record contains a five-character hand and an integer bid.
- Card ranks from lowest to highest are: `2 3 4 5 6 7 8 9 T J Q K A`.
Hand categories from weakest to strongest are:
1. High card
2. One pair
3. Two pair
4. Three of a kind
5. Full house
6. Four of a kind
7. Five of a kind
Comparison rules:
- A hand with a stronger category wins.
- If two hands have the same category, compare their cards from left to right using the rank order above.
- Sort all hands from weakest to strongest.
- The total score is the sum over all sorted hands of `1-based_rank * bid`.
Extension if time allows:
- Add a wildcard mode where `J` can represent any rank that maximizes the hand category, while still being treated as the lowest card for left-to-right tie breaking.
Quick Answer: This question evaluates API and system design, configurable string processing and state management for a logger (including search and complexity considerations), as well as algorithmic reasoning for card-hand ranking, sorting, and tie-breaking logic.
Part 1: Simulate a Configurable Logger
Implement a logger simulator that processes a list of command strings.
The logger supports four configurable behaviors:
1. Remove all occurrences of a configured substring.
2. Truncate the message to at most a configured maximum length.
3. Convert the entire message to uppercase.
4. Store transformed messages internally for later searching.
Default state:
- removal is disabled
- truncation is disabled
- uppercase is disabled
- storing is disabled
Commands:
- SET_REMOVE <text> : enable substring removal using <text>. If <text> is omitted, the removal string becomes the empty string.
- CLEAR_REMOVE : disable substring removal.
- SET_TRUNCATE <k> : enable truncation with integer k. If k is negative, treat it as 0.
- CLEAR_TRUNCATE : disable truncation.
- SET_UPPERCASE 1 or SET_UPPERCASE 0 : enable or disable uppercase conversion.
- SET_STORE 1 or SET_STORE 0 : enable or disable storing.
- LOG <message> : transform the message and produce output.
- SEARCH <query> : return all stored transformed messages containing query as a substring.
Transformation order for LOG is always:
1. remove substring
2. truncate
3. uppercase
Edge rules:
- A LOG command with no message means the empty string.
- The special message token <NULL> should be treated as null input, which becomes the empty string.
- An empty removal string does nothing.
- SEARCH is case-sensitive.
- Stored values are the transformed messages.
Return a list containing one entry for each LOG or SEARCH command:
- LOG contributes [transformed_message]
- SEARCH contributes the list of matching stored messages in insertion order
A naive SEARCH that scans all stored messages is acceptable. In a follow-up, you could discuss faster search with an index or suffix-based structure if the stored history becomes very large.
Constraints
- 1 <= len(commands) <= 20000
- Each command is one of the formats described above
- Total length of all command strings is at most 200000
- SEARCH should be implemented case-sensitively
Examples
Input: ["SET_REMOVE ab", "SET_TRUNCATE 5", "SET_UPPERCASE 1", "SET_STORE 1", "LOG xxababy", "SEARCH Y"]
Expected Output: [["XXY"], ["XXY"]]
Explanation: After removal, truncation, and uppercase, 'xxababy' becomes 'XXY'. It is stored, and SEARCH Y finds it.
Input: ["SET_REMOVE", "SET_TRUNCATE -3", "LOG <NULL>", "SET_STORE 1", "LOG abcdef", "SEARCH"]
Expected Output: [[""], [""], [""]]
Explanation: Empty removal does nothing. Negative truncation becomes 0. '<NULL>' is treated as empty input. SEARCH with no query uses the empty string, so it matches every stored message.
Input: ["SET_STORE 1", "LOG Hello", "SET_UPPERCASE 1", "LOG hello", "SET_UPPERCASE 0", "SEARCH HEL", "SEARCH hel"]
Expected Output: [["Hello"], ["HELLO"], ["HELLO"], []]
Explanation: Search is case-sensitive, so only 'HELLO' matches 'HEL', and nothing matches lowercase 'hel'.
Input: ["SET_STORE 1", "SET_REMOVE a", "LOG banana", "CLEAR_REMOVE", "SET_TRUNCATE 3", "LOG banana", "SEARCH bn"]
Expected Output: [["bnn"], ["ban"], ["bnn"]]
Explanation: The first LOG removes 'a' to get 'bnn'. After removal is cleared and truncation is set to 3, the second LOG becomes 'ban'. SEARCH bn matches only 'bnn'.
Hints
- Keep four pieces of mutable state: removal substring, truncation length, uppercase flag, and store flag.
- Be careful with edge cases: empty removal string should not loop forever, and negative truncation should behave like length 0.
Part 2: Rank Custom Five-Card Hands
You are given a list of records for a simplified five-card game. Each record is a string in the form 'HAND BID', where HAND is exactly 5 characters and BID is an integer.
Card ranks from weakest to strongest are:
2 3 4 5 6 7 8 9 T J Q K A
Hand categories from weakest to strongest are:
1. High card
2. One pair
3. Two pair
4. Three of a kind
5. Full house
6. Four of a kind
7. Five of a kind
Comparison rules:
- A stronger category always wins.
- If two hands have the same category, compare their cards from left to right using the rank order above.
- Do not reorder cards inside a hand.
Sort all hands from weakest to strongest. If a hand ends up in 1-based position i in this sorted order, it contributes i * bid to the total score.
Return the total score.
Constraints
- 1 <= len(records) <= 100000
- Each HAND has length exactly 5
- HAND contains only characters from 23456789TJQKA
- 0 <= BID <= 10^9
- Use 64-bit arithmetic in languages that need it
Examples
Input: ["32T3K 765", "T55J5 684", "KK677 28", "KTJJT 220", "QQQJA 483"]
Expected Output: 6440
Explanation: This is the standard sample for the non-wildcard rules.
Input: ["AAAAA 10"]
Expected Output: 10
Explanation: Edge case: a single hand always has rank 1.
Input: ["33332 1", "2AAAA 10"]
Expected Output: 12
Explanation: Both are four of a kind. Compare left to right: '2AAAA' is weaker than '33332'.
Input: ["23456 5", "A23A4 2", "23432 3"]
Expected Output: 18
Explanation: The hands are high card, one pair, and two pair respectively, so they stay in that order.
Hints
- A hand's category depends only on the frequency counts of its 5 cards.
- For sorting, build a key like (category_strength, rank_of_card_1, rank_of_card_2, ..., rank_of_card_5).
Part 3: Rank Five-Card Hands with Wildcard Jokers
This problem uses the same game and scoring rules as Part 2, but with one change:
Wildcard mode:
- 'J' acts as a wildcard that can represent any rank when determining the best possible hand category.
- However, for left-to-right tie breaking, the original hand is still compared as written, and 'J' is treated as the weakest card.
Tie-break rank order in wildcard mode is therefore:
J 2 3 4 5 6 7 8 9 T Q K A
Examples:
- 'T55J5' can become four of a kind.
- 'JJJJJ' is five of a kind.
- If two hands both become the same category, compare the original 5-character strings left to right using the wildcard tie-break order above.
Input records are strings in the form 'HAND BID'. Sort all hands from weakest to strongest under these wildcard rules, then return the total score: sum of 1-based_rank * bid.
Constraints
- 1 <= len(records) <= 100000
- Each HAND has length exactly 5
- HAND contains only characters from 23456789TJQKA
- 0 <= BID <= 10^9
- Use 64-bit arithmetic in languages that need it
Examples
Input: ["32T3K 765", "T55J5 684", "KK677 28", "KTJJT 220", "QQQJA 483"]
Expected Output: 5905
Explanation: This is the standard sample for wildcard mode.
Input: ["JJJJJ 7"]
Expected Output: 7
Explanation: Edge case: all jokers become five of a kind, and the only hand gets rank 1.
Input: ["JKKK2 5", "QQQQ2 3"]
Expected Output: 11
Explanation: Both hands are four of a kind after optimization, but tie breaking uses the original hands and J is the lowest card, so 'JKKK2' is weaker.
Input: ["T55J5 4", "QQQJA 2", "KTJJT 3"]
Expected Output: 17
Explanation: All three hands become four of a kind. Tie breaking on the original hands gives the order T55J5, QQQJA, KTJJT.
Hints
- Separate the number of J cards from the counts of the other ranks. If the hand is all J, it is automatically five of a kind.
- To maximize only the category, it is enough to add all jokers to the largest existing group. But for tie breaking, use the original hand with J as the lowest rank.