The Trip Aggregator

A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
medium
Seniority
L3
Asked in
Booking.com

Problem

The per-user destination counts on our travel dashboard look inflated for anyone who revisits a city, so we're recomputing them from the raw trips records. For every user, report their total hours traveled and how many different destinations they've visited, listed alphabetically by user_id.

Travel records hold patterns waiting to be surfaced.

Example

Input:

trips = [{"origin":"NYC","user_id":"u1","destination":"LA","duration_hours":6},{"origin":"LA","user_id":"u1","destination":"NYC","duration_hours":6},{"origin":"NYC","user_id":"u2","destination":"LA","duration_hours":6},{"origin":"NYC","user_id":"u1","destination":"LA","duration_hours":6}]

Output:

[{"user_id":"u1","total_hours":18,"unique_destinations":2},{"user_id":"u2","total_hours":6,"unique_destinations":1}]

Worked solution and explanation

Two aggregates that disagree about repeats

This is SUM(duration_hours) and COUNT(DISTINCT destination) under GROUP BY user_id, wearing a Python costume. The whole problem lives in the gap between those two aggregates. Hours are additive: every trip counts, repeats included. Destinations are not: a second 'LA' trip adds six hours and zero destinations. Candidates who keep one counter per user, or who dedupe trips before totaling, get one of the two numbers wrong for exactly the users who revisit a place. That is 'u1' in the example: 3 destinations instead of 2, or 12 hours instead of 18.

Step 1: Key the state by user_id

Each user needs two accumulators that behave differently: an integer for hours and a set for destinations. Create both the first time a user_id shows up. The origin field never enters the state.

Step 2: Add every trip's hours, but only new destinations

Every trip adds its duration_hours, repeats included, because that time was really spent. The destination goes into the set, which quietly ignores a second 'LA'. The set's len() is then the distinct count, with no cleanup pass afterwards.

Step 3: Emit users in sorted key order

Build one dict per user with user_id, total_hours and unique_destinations, walking sorted(users.keys()) rather than the dict's insertion order. Trips arrive in log order, and the first user the log mentions is not necessarily the first alphabetically.

Dict-based grouping with set for unique counting
def aggregate_trips(trips: list) -> list:
    users = {}
    for trip in trips:
        uid = trip["user_id"]
        if uid not in users:
            users[uid] = {"total_hours": 0, "destinations": set()}
        users[uid]["total_hours"] += trip["duration_hours"]
        users[uid]["destinations"].add(trip["destination"])
    result = []
    for uid in sorted(users.keys()):
        entry = {
            "user_id": uid,
            "total_hours": users[uid]["total_hours"],
            "unique_destinations": len(users[uid]["destinations"])
        }
        result.append(entry)
    return result
One counter per user
Bumping a count on every trip gives 'u1' a destination count of 3, because the repeat 'LA' trip counts again. Deduping trips first fixes that count and breaks total_hours instead, dropping 'u1' to 12.
A counter and a set per user
The integer takes every duration_hours, and the set takes every destination but keeps one copy of each. 'u1' lands at 18 hours and 2 destinations, which matches the example.

Common follow-up questions