Approach: Build a lookup of each user's unique event dates, normalize input date formats, then for each signup check whether the user had any event strictly after signup and within N days (N ∈ {1,7,30}). Count signups and returning users per N and compute retention rates.python
from collections import defaultdict
from datetime import datetime, date, timedelta
from typing import List, Tuple, Dict, Union
DateLike = Union[str, date, datetime]
def _to_date(d: DateLike) -> date:
if isinstance(d, date) and not isinstance(d, datetime):
return d
if isinstance(d, datetime):
return d.date()
# assume ISO format string like '2023-05-01'
return datetime.fromisoformat(d).date()
def rolling_retention(signups: List[Tuple[str, DateLike]],
events: List[Tuple[str, DateLike]]) -> Dict[int, float]:
"""
Returns retention rates for N in [1,7,30] as percentages (0-100).
Count an event as a return if event_date > signup_date and event_date <= signup_date + N days.
Duplicate events and multiple events on same day are handled by using unique dates.
"""
Ns = [1, 7, 30]
# Build user -> set of event dates (unique)
user_events = defaultdict(set)
for user_id, ev in events:
user_events[user_id].add(_to_date(ev))
# Counters
total_signups = 0
returning = {n: 0 for n in Ns}
for user_id, s in signups:
signup_date = _to_date(s)
total_signups += 1
ev_dates = sorted(user_events.get(user_id, []))
if not ev_dates:
continue
# For each N, check if any event date within (signup_date, signup_date + N]
for n in Ns:
window_end = signup_date + timedelta(days=n)
found = False
# iterate through sorted unique event dates until beyond window_end
for ed in ev_dates:
if ed <= signup_date:
continue
if ed > window_end:
break
found = True
break
if found:
returning[n] += 1
# Compute percentages; handle zero signups
if total_signups == 0:
return {n: 0.0 for n in Ns}
return {n: (returning[n] / total_signups) * 100.0 for n in Ns}
Key points:- We treat same-day events as non-returns (event_date must be > signup_date). Adjust comparison if you want to include same-day returns.- Duplicate events are deduplicated by using a set of dates per user.Time complexity: O(E + U * E_u * |Ns|) where E total events, U signups, E_u average events per user. Space: O(E) to store events.Edge cases:- No signups → return zeros.- Users with events but no signup (ignored).- Mixed input types for dates handled (ISO strings, date, datetime).