An expiry classifier assigns records to time states using declared inclusive and exclusive boundaries.
Python expiry exercise: separate pending, active and expired records at exact boundaries
Operation contract
The exercise accepts an owned list of at most sixty-four unique receipt records and a bounded exact-integer clock. A record is pending before its creation time, active from creation through the instant before created plus TTL, and expired at or after that endpoint. Validate every record before returning any result. A duplicate identity or Boolean TTL fails the batch instead of quietly choosing one record. Results are sorted IDs so caller row order cannot change their presentation.
Failure and ownership boundary
The clock and record times use the same abstract integer unit; the fixture does not interpret wall-clock zones or accept serialized timestamps. It never deletes or renews records. Python TTL cache: expire at a defined boundary and use an injected clock, Python datetime: require an offset before comparing timestamps and Python reconciliation exercise: reject duplicate IDs before comparing ledgers add those policies separately. Test the exact creation and expiry instants, not only values comfortably inside the interval.
Working program
def classify_expiry(records, now):
if type(now) is not int or not 0 <= now <= 1000000 or type(records) is not list or len(records) > 64:
raise ValueError("bounded records and clock required")
seen, states = set(), {"pending": [], "active": [], "expired": []}
for record in records:
if type(record) is not dict or set(record) != {"receipt_id", "created", "ttl"}:
raise ValueError("exact fields required")
identifier, created, ttl = record["receipt_id"], record["created"], record["ttl"]
if type(identifier) is not str or not identifier.isascii() or not identifier.isalnum() or not 1 <= len(identifier) <= 16 or identifier in seen or type(created) is not int or not 0 <= created <= 1000000 or type(ttl) is not int or not 1 <= ttl <= 100:
raise ValueError("unique ID and bounded times required")
seen.add(identifier)
state = "pending" if now < created else "active" if now < created + ttl else "expired"
states[state].append(identifier)
return {state: sorted(identifiers) for state, identifiers in states.items()}
records = [{"receipt_id": "R41", "created": 100, "ttl": 10},
{"receipt_id": "R42", "created": 110, "ttl": 5},
{"receipt_id": "R43", "created": 120, "ttl": 5}]
print(classify_expiry(records, 110))
try:
classify_expiry(records + [dict(records[0])], 110)
except ValueError:
print("duplicate batch rejected")Output
{'pending': ['R43'], 'active': ['R42'], 'expired': ['R41']}
duplicate batch rejectedCosts and limits
Validation and classification take O(n), with up to O(n log n) sorting and O(n) private result storage. The supplied records are left unchanged even on rejection. This is a snapshot calculation; applying its result to mutable service state later would require a concurrency policy.
Common Mistakes
- State whether the expiry endpoint is active or expired.
- Reject duplicate identities before returning a batch result.
Connected lessons
Python TTL cache: expire at a defined boundary and use an injected clock, Python reconciliation exercise: reject duplicate IDs before comparing ledgers, Python datetime: require an offset before comparing timestamps.
Check this related boundary
Python testing: inject a clock to test expiry at the exact boundary.
