Simulate Constant-Time Random Courier Selection
Company: DoorDash
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
## Simulate Constant-Time Random Courier Selection
### Problem
Implement `simulate_random_picker(operations, random_values)`.
The picker maintains a changing set of courier IDs and supports `add`, `remove`, and `pick`. A production picker would obtain a random integer for each `pick`; this deterministic version receives those integers in `random_values` so every language produces the same output.
### Function Contract
```text
simulate_random_picker(operations, random_values) -> picked_ids
```
`picked_ids` is a JSON array of strings.
### Input Encoding
- `operations` is a JSON array. Each operation is one of:
- `["add", courier_id]`
- `["remove", courier_id]`
- `["pick"]`
- Every `courier_id` is a nonempty case-sensitive string of at most `64` ASCII characters.
- `random_values` is a JSON array of nonnegative integers. It contains exactly one value for each `pick` operation, in the same order.
- The operation stream is valid: `add` names an inactive ID, `remove` names an active ID, and `pick` occurs only when at least one ID is active. A removed ID may be added again later.
### Selection and Output Contract
Maintain active couriers in a dense zero-based array. When processing the next `pick`, consume the next value `r` from `random_values`, compute `r % active_count`, and return the courier currently stored at that index.
Return a JSON array containing the selected courier IDs in pick order. `add` and `remove` operations do not directly append anything to the result.
### Examples
```text
operations = [["add", "a"], ["add", "b"], ["pick"], ["remove", "a"], ["pick"]]
random_values = [3, 8]
result = ["b", "b"]
```
```text
operations = [["add", "a"], ["add", "b"], ["add", "c"], ["remove", "b"], ["pick"], ["add", "b"], ["pick"]]
random_values = [1, 1]
result = ["c", "c"]
```
The second example observes the swap-with-last removal rule: removing `b` moves `c` into index `1`.
### Requirements
- Use a dense array plus a map from courier ID to its current array index.
- `add`, `remove`, and `pick` must each take expected `O(1)` time.
- To remove an ID, move the last array element into the removed element's slot, update that moved ID's map entry, and then shorten the array.
- Support at most `200,000` operations and `200,000` random values.
- Each random value is in the range `0` through `2,147,483,647`.
- The active set may contain at most `100,000` courier IDs.
- Use `O(a)` auxiliary space, where `a` is the maximum active-set size.
```hint Keep the index range dense
Focus on what must change in both data structures when the removed courier is not already the last array element.
```
### Discussion Requirements
1. Identify the stale-index bug that occurs if the last courier is moved but its map entry is not updated.
2. Explain how concurrent `add`, `remove`, and `pick` calls could observe inconsistent array and map state.
3. Compare synchronizing an entire method with using a smaller synchronized block around only the shared-state mutation.
4. Explain why calling a slow external API while holding the picker lock can block unrelated operations when that dependency times out.
5. Describe the new coordination, ownership, and randomness challenges that appear if the picker is distributed across processes.
Quick Answer: Maintain a changing set of courier IDs with expected constant-time add, remove, and deterministic random-pick operations. The challenge probes coordinated array and index-map state, swap-based removal correctness, concurrency boundaries, external-call isolation, and distributed extensions.
Implement `simulate_random_picker(operations, random_values)`. Maintain active courier IDs in a dense zero-based array. A valid operation is `['add', id]`, `['remove', id]`, or `['pick']`; add names an inactive ID, remove names an active ID, and pick occurs only when nonempty. For each pick, consume the next supplied integer `r`, select index `r % active_count`, and append that courier. Remove by moving the last courier into the vacated slot, updating its index, and shortening the array. Return picked IDs in order.
Constraints
- At most 200,000 valid operations and 200,000 random values are supplied.
- Each courier ID is a nonempty case-sensitive ASCII string of at most 64 characters.
- There is exactly one random value in [0, 2,147,483,647] per pick.
- At most 100,000 couriers are active; removed IDs may be added later.
Examples
Input: ([['add', 'only'], ['pick']], [0])
Expected Output: ['only']
Explanation: One active courier is always selected.
Input: ([['add', 'a'], ['add', 'b'], ['pick'], ['remove', 'a'], ['pick']], [3, 8])
Expected Output: ['b', 'b']
Explanation: The first source example wraps modulo two and then has one survivor.
Hints
- When removal moves the last courier, trace which two pieces of state must reflect its new dense index.