Quick Overview

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.

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.

Overview: 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.

Read the full Rippling Software Engineer interview experience this question came from

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.

Hints

  1. Keep four pieces of mutable state: removal substring, truncation length, uppercase flag, and store flag.
  2. 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.

Approach

The task is to sort hands from weakest to strongest, then sum position * bid. The solution encodes each hand's strength as a sortable tuple and lets a single sort do the ranking. Card ranks. rank = {c: i for i, c in enumerate("23456789TJQKA")} maps each card to an integer 0..12 matching the weakest-to-strongest order, so larger numbers mean stronger cards. Hand category. category(hand) counts how many times each card appears with Counter, then sorts those counts descending and pads with [0, 0] so indexing is always safe. The shape of the counts uniquely identifies the category: - [5] → five of a kind (6) - [4,1] → four of a kind (5) - [3,2] → full house (4) - [3,1,1] → three of a kind (3) - [2,2,1] → two pair (2) - [2,1,1,1] → one pair (1) - otherwise high card (0) The checks are ordered so the most distinctive shapes are tested first, and counts[1] disambiguates full house vs. three-of-a-kind and two-pair vs. one-pair. Sort key. Each record becomes (category, [rank[c] for c in hand], idx, bid). Sorting by (category, card_ranks, idx) applies the rules exactly: a stronger category wins, and on a tie Python compares the per-card rank list left to right without reordering — precisely the tiebreak rule. idx is just a deterministic final tiebreak; truly equal hands produce the same total regardless of order. Scoring. Iterating the sorted list with 1-based index i, the answer accumulates i * bid. Python's big integers make the BID up to 10^9 and 64-bit requirement a non-issue.

Time complexity: O(n log n), where n is the number of records. The sort dominates; per-hand classification (Counter over exactly 5 cards) and key construction are O(1).

Space complexity: O(n) for the `parsed` list of sortable tuples (each holds a fixed 5-element rank list, so per-record space is constant).

Hints

  1. A hand's category depends only on the frequency counts of its 5 cards.
  2. 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.

Approach

Goal. Sort every hand from weakest to strongest under wildcard rules, then sum rank * bid over the 1-based sorted order. Two independent orderings. A hand's strength is a pair: its category (what the best possible hand becomes with jokers) and, as a tiebreaker, its original five characters compared left-to-right. The code captures both in one sort key. Tie-break order. rank = {c: i for i, c in enumerate("J23456789TQKA")} maps each card to a strength index where J is weakest (0) and A strongest. Each hand becomes [rank[c] for c in hand], so list comparison reproduces the left-to-right rule exactly. Category with wildcards. category(hand) counts cards with Counter, then pops out the jokers. The key insight: to maximize the category, you always pour all jokers into the largest existing group (groups[0] += jokers). That greedily produces the highest count and is provably optimal here. The all-joker case (JJJJJ → counts empty) is handled separately as five of a kind (6). After padding groups with zeros, a cascade of comparisons maps the two largest group sizes to a category code 0–6 (high card → five of a kind). Sorting. Each record is parsed into (category, ranks, idx, bid) and sorted by (category, ranks, idx). Because idx is included, ties are fully deterministic (stable on input order, though equal (category, ranks) are effectively identical hands). Finally it accumulates i * bid over the enumerated order. Using plain Python int avoids overflow even with bids up to 10^9 and 10^5 records.

Time complexity: O(n log n), where n is the number of records. Each hand is classified and keyed in O(1) (fixed 5 cards), and the dominant cost is sorting n hands.

Space complexity: O(n) for the parsed list of sort keys (each key holds a fixed-size 5-element rank list).

Hints

  1. 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.
  2. 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.

Community answers

Answer by m3ajak6

damn it, the question 2 was asked and I missed it here

Answer by m3ajak6

part 1 is a separate question than part 2, consider separating them

Loading coding console...

Show the approach

Approach

This is a stateful command interpreter. The function walks the commands list once, keeping four pieces of mutable config plus a stored history list, and emits output only for LOG and SEARCH.

Parsing. Each command is split once on the first space (raw.split(" ", 1)), giving cmd and an arg (empty string when no argument is present). This cleanly handles bare commands like SET_REMOVE (arg becomes "") and LOG with no message.

Config state (all start disabled):

  • remove_sub — substring to strip, or None when disabled.
  • truncate_len — max length, or None.
  • uppercase, store_enabled — booleans set by comparing arg to "1".

SET_*/CLEAR_* commands just mutate this state; SET_TRUNCATE parses the arg as int.

LOG transformation, applied in the required order:

  1. <NULL> (and missing arg) collapse to "".
  2. If remove_sub is enabled and non-empty, remove every occurrence via str.replace — the remove_sub not in (None, "") guard correctly makes an empty removal string a no-op.
  3. If truncation is on, slice message[:max(0, truncate_len)], so a negative k acts as 0.
  4. If uppercase, apply .upper().

The transformed message is appended to stored only when store_enabled, and [message] is appended to results.

SEARCH scans stored in insertion order, keeping every entry where query is a substring (case-sensitive, since Python's in is exact). The list of matches is appended to results.

Why it's correct: state changes between commands are honored because everything is read at LOG time; stored values are the transformed messages, matching the spec; and the guards faithfully encode each edge rule.

Space complexity:
O(H + R), where H is the total length of stored transformed messages and R is the total size of the returned `results` (one list per LOG/SEARCH, including all matched strings).