← 返回 xai 的题目列表Twitter Spaces — Aggregate Active Hours
类型:qbank
Onsite backend round. Given a chronological event log of `(operation, space_id, user, timestamp)` records covering `create / join / leave`, compute the total user-active-time per Twitter Space. Follow-up extends the same data into a streaming top-K of the spaces with the most concurrent users.
Requirements
Input: chronological list of records (operation, space_id, user_id, timestamp), where operation ∈ {create, join, leave}. create implies the creator is also joined.
Return: dict[space_id] → total_active_time, summed across all users in that space. A user contributes (leave_ts − join_ts) per session.
Input:
["create", "abc", "user_1", 1234567000]
["join", "abc", "user_2", 1234567100]
["leave", "abc", "user_2", 1234567300]
["create", "def", "user_2", 1234568000]
["leave", "def", "user_2", 1234568500]
["leave", "abc", "user_1", 1234569000]
Output:
{"abc": 2200, "def": 500}
Explanation: user_1 was in abc for 2000 seconds, user_2 was in abc for 200 seconds (total 2200); user_2 was in def for 500 seconds.
The canonical OA exposes two entry points — a batch aggregator and a real-time tracker:
def calculate_active_time(logs: list[list], current_time: int | None = None) -> dict[str, int]: ...
# logs entries are [operation, space_id, user_id, timestamp]; operation in {create, join, leave}.
# Returns {space_id: total_active_seconds} summed across all users. create implies the creator joins.
class TwitterSpacesTracker:
def process_event(self, operation: str, space_id: str, user_id: str, timestamp: int) -> None: ...
def get_top_k(self, k: int) -> list[tuple[str, int]]: ... # spaces by current active-user count, count desc
Examples
The sample above is the only example surfaced in interview reports — use it as the unit test.
Notes
Track (space_id, user_id) → join_ts in a hashmap; emit a duration on every leave and clear the entry.
A create event implicitly joins the creator — treat it as create + join for the same user.
The spec guarantees a user is in at most one Space at a time — they always leave their current Space before joining another (or re-joining the same one). That lets a single user_id → (space_id, join_ts) map stand in for the (space_id, user_id) key above; you never have to track one user across concurrent Spaces.
Watch for users who never explicitly leave (round end with sessions still open). The reference solution treats those as not contributing — the interviewer expects you to ask whether to close them at the final timestamp.
The follow-up is streaming top-K spaces by active user count, in real time. Maintain a per-space concurrent count and a max-heap / order-statistics tree keyed on count; update on every join / leave.
space_id and user_id are opaque strings — do not assume integer IDs.
Defensive input handling the spec demands: sort logs by timestamp (input may arrive unordered), drop exact-duplicate log entries, ignore a leave with no matching join, and sum every session for a user who leaves and re-joins.
current_time is an optional end-of-window argument: sessions still open at the end are closed at current_time if it is supplied, otherwise that open session contributes nothing.
For get_top_k, a bucket array indexed by active-user count gives O(K) retrieval; a size-K min-heap gives O(N log K). Move a space between buckets on each join/leave and drop it when its count hits zero.
Preparation
Write the base function in 10 minutes; reserve 20 minutes for the streaming top-K extension.
Practice both data structures for top-K in a stream: bucketed counts (for bounded ranges) and a SortedList / heap with lazy deletion.
Be able to explain how this generalizes to a sliding window (last 5 minutes of activity) — a likely second follow-up.
The streaming top-K-by-concurrent-users follow-up is the same shape as the canonical Ad-Click-Aggregator / Top-K-by-time-bucket SD problem: bucket events into fixed-width time slices (e.g. 1-minute granularity), maintain a per-space counter inside each bucket, and run top-K over the rolling sum of the last W / bucket_width buckets. Picking the bucket width is a tradeoff between query latency (more buckets = finer rollup window) and memory (more buckets = more counters retained). A streaming framework (Flink / Kafka Streams) is the natural production answer if the interviewer pushes past the in-memory version.