Katkat

Count User Journey Prefixes

WhatnotSoftware Engineer · Online Assessment

Last updated

Process timestamped user activity logs to reconstruct chronological action sequences and count the frequency of user journey prefixes up to length k.

Problem Statement

In an e-commerce marketplace, understanding how users navigate the platform helps identify common drop-off points and popular discovery paths. You are given a list of user activity logs, where each log entry contains a user_id, an integer timestamp, and an action representing a page visit or interaction.

Your task is to reconstruct each user's chronological journey and count how many users completed each path prefix of length 1 up to k.

Specifically:

  1. Group the logs by user_id and sort each user's actions in ascending order of timestamp.
  2. For each user, extract every prefix of their action sequence with length between 1 and min(k, len(user_actions)).
  3. Count the number of unique users who experienced each prefix.

Return the counts as a dictionary (or hash map) where the key is a tuple of action strings representing the prefix, and the value is the integer count of users.

Interface

def count_user_prefixes(logs: list[tuple[str, int, str]], k: int) -> dict[tuple[str, ...], int]:
    """
    Args:
        logs: List of (user_id, timestamp, action) tuples.
        k: Maximum prefix length to consider (1 <= k).

    Returns:
        A mapping from prefix tuples to the number of distinct users who have that prefix.
    """

Input & Output Format

  • Input:
    • logs: A list of tuples (user_id, timestamp, action) where user_id is a string, timestamp is an integer, and action is a string.
    • k: An integer representing the maximum length of prefixes to count.
  • Output: A dictionary mapping tuple[str, ...] to int.

Examples

Example 1

logs = [
    ("u1", 100, "home"),
    ("u2", 90, "home"),
    ("u1", 110, "item"),
    ("u1", 105, "search"),
    ("u2", 95, "search"),
    ("u2", 120, "cart"),
    ("u3", 50, "stream")
]
k = 2

output = count_user_prefixes(logs, k)
# Output:
# {
#     ("home",): 2,
#     ("home", "search"): 2,
#     ("stream",): 1
# }

Explanation:

  • u1 journey: ["home", "search", "item"]. Prefixes up to length 2: ("home",) and ("home", "search").
  • u2 journey: ["home", "search", "cart"]. Prefixes up to length 2: ("home",) and ("home", "search").
  • u3 journey: ["stream"]. Prefixes up to length 2: ("stream",).

Example 2

logs = [
    ("u1", 10, "login"),
    ("u1", 20, "login")
]
k = 3

output = count_user_prefixes(logs, k)
# Output:
# {
#     ("login",): 1,
#     ("login", "login"): 1
# }

Constraints

  • 1 <= len(logs) <= 10^5
  • 1 <= k <= 10
  • 1 <= len(user_id), len(action) <= 30
  • 0 <= timestamp <= 10^9
  • All (user_id, timestamp) pairs are unique within logs.

Hints

Hint 1

Group log entries by user_id first so you can process each user independently.

Hint 2

Sort each user's entries by timestamp ascending to construct their correct chronological sequence of actions.

Hint 3

Iterate through prefix lengths from 1 to min(k, len(actions)) for each user and increment the prefix counter in a hash map.

Follow-up questions

  • How would your approach change if the logs were a continuous, out-of-order stream that could not fit entirely in memory?
  • If k was large (e.g., k = 1000) and we needed to query prefix counts dynamically, what data structure (e.g., a Trie) would be more space- and query-efficient?
  • How would you handle sessions where an inactivity gap of more than 30 minutes splits a user's events into distinct journeys?