Pebellslearn by building
0%
RoadmapMatrixCatalogSandboxSign in
Foundations: Complexity & Arrays  ›  Capstone Project

Capstone — Library Dashboard

35 minOne O(t + p) pass that combines an index, counts and a sliding window
You're building a piece ofTunebox — the library
This piece — library_dashboard(): Ties the index, playlist and sessions into one library summary.
Scenario Tunebox's home screen opens on a library dashboard. It has to summarise a messy library and a long play log on every refresh, so it combines the song index, the play log array and the session finder in one O(t + p) function.
Your task
Build library_dashboard(tracks, plays, budget). Index the library by id (the first copy of a repeated id wins), ignore plays of ids that aren't in the library, then return {"tracks", "plays", "minutes", "top_track", "top_artist", "session"}: unique tracks, valid plays, minutes played, the most-played title (ties: library order), the artist with the most minutes (ties: alphabetical), and the titles of the longest run of consecutive valid plays whose minutes fit within budget (ties: earliest; none fits → []). Leaders are None when nothing valid was played. Example: library_dashboard([{"id": "t1", "title": "Glow", "artist": "Nova", "mins": 4}, {"id": "t2", "title": "Tide", "artist": "Echo", "mins": 3}], ["t1", "t2", "t9", "t2"], 6) → {"tracks": 2, "plays": 3, "minutes": 10, "top_track": "Tide", "top_artist": "Echo", "session": ["Tide", "Tide"]}.

Capstone — Library Dashboard

Every function in this section built one piece of Tunebox's library: the scale planner, the playlist, the session finder, the song index. Now they run together. The home screen shows a library dashboard — how big the library is, what's been played, who you listen to most, and the longest session that fits your free time — and one function, library_dashboard(), computes it on every refresh.

The catch: a real library is messy and a real play log is long. The dashboard must stay O(t + p) for t tracks and p plays — a pass or two over each, no loop inside a loop, no in on a list, no pop(0).

What you're building

library_dashboard(tracks, plays, budget)
  • tracks — the library, a list of dicts like {"id": "t1", "title": "Glow", "artist": "Nova", "mins": 4}. A messy import can list the same id twice; the first copy is the real track.
  • plays — the play log: track ids in the order they were played. Ids that aren't in the library (deleted tracks) are ignored.
  • budget — a number of minutes.

The rules

Work in this order — each step feeds the next:

  1. Index the library. id → track, keeping the first copy of each id.
  2. Clean the log. Keep only plays whose id is in the index, in their original order.
  3. Count. Plays per track, and minutes played per artist.
  4. Pick the leaders. top_track is the title with the most plays — ties go to the track that comes first in the library. top_artist has the most minutes played — ties go alphabetically. Both are None when there are no valid plays.
  5. Find the session. The longest run of consecutive valid plays whose minutes add up to at most budget, as a list of titles. Ties go to the earliest run; if no single play fits, it's [].

The exact shape to return

{
    "tracks": 5,                           # unique ids in the library
    "plays": 8,                            # valid plays
    "minutes": 30,                         # total minutes of valid plays
    "top_track": "Tide",                   # most plays (ties: library order)
    "top_artist": "Nova",                  # most minutes (ties: alphabetical)
    "session": ["Glow", "Tide", "Tide"],   # longest run within budget, as titles
}

Which piece does which job

step technique from cost
index, first copy wins hash table with a not in check Hash tables O(t)
clean the log O(1) membership per play Hash tables O(p)
count plays and minutes counting with dict.get Hash tables O(p)
top track, library order one walk over the index — dicts keep insertion order Hash tables O(t)
longest session variable-size sliding window over the log Two pointers O(p)
session titles one slice of the clean log Arrays O(p)

Add them up: O(t + p). Each step is short; the bugs live in the details. Here they are one at a time.

Trap 1 — in on the wrong structure

Cleaning the log asks "is this id in the library?" once per play. Here's what that costs when the ids sit in a list:

What does this print?
library_ids = ["t1", "t2", "t3", "t4"]
plays = ["t2", "t9", "t4", "t2"]
checks = 0
for tid in plays:
    for known in library_ids:          # what `tid in library_ids` really does
        checks += 1
        if known == tid:
            break
print(checks)

Trap 2 — the duplicate id

A dict comprehension is the natural way to build an index, and it gets the duplicate rule backwards:

broken — fix it

The first copy of a repeated id is the real track, so this should print Tide. It prints the duplicate instead.

Trap 3 — ties

max() and min() return the first best item they meet, so the order you walk in is your tie-break rule:

What does this print?
counts = {"t3": 2, "t1": 2, "t2": 1}
library_order = ["t1", "t2", "t3"]
print(max(counts, key=counts.get), max(library_order, key=counts.get))

So for top_track, walk the index — it's in library order — and replace the leader only on strictly more plays:

python

"Most minutes, ties alphabetical" needs two rules at once. A tuple key does it: the smallest (-minutes, name) has the most minutes, then the earliest name.

python

Trap 4 — the window runs over the clean log

The session is a window over the cleaned log, measured in minutes: a deleted track has no minutes to count, and looking its id up in the index would raise KeyError. Step through the window on the minutes of a clean log:

step through it
1mins = [4, 3, 3, 2, 6]
2budget = 9
3start = total = 0
4best = [0, 0]
5for end in range(len(mins)):
6 total += mins[end]
7 while total > budget:
8 total -= mins[start]
9 start += 1
10 if end + 1 - start > best[1] - best[0]:
11 best = [start, end + 1]
12print(best)

A strict > keeps the earliest of two equally long runs; >= would keep the latest. Once you have best, the titles are one slice away: log[best[0]:best[1]].

Build it in pieces

Steps 1 and 2 on the sample library — run it, then carry on from here in the editor:

python

🎯 Your turn

Write library_dashboard(tracks, plays, budget) and return all six keys. Follow the rules in order: index (first copy wins) → clean log → counts → leaders → session.

With the sample library and play log above, library_dashboard(tracks, plays, 10){"tracks": 5, "plays": 8, "minutes": 30, "top_track": "Tide", "top_artist": "Nova", "session": ["Glow", "Tide", "Tide"]}

Hint — reuse the pieces you already built: the index loop from Trap 2, the tuple key from Trap 3, and session_within's window from the two-pointers lesson.

Then press ▶ Run, tap the Live App try chips to call it with different inputs, and hit ✓ Check. All green = Tunebox's library is complete. 🎉

Practice — level up

0/5 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upClean up the import

A messy import lists some track ids more than once. Write unique_library(tracks) returning the titles of the tracks to keep — the first copy of each id — in library order.

dedupehashing
DrillMinutes per artist

Write artist_minutes(tracks, plays) returning a dict of artist → total minutes played, where plays is a list of track ids. Ids in tracks are unique here; plays of ids that aren't in tracks are ignored, and artists with no plays don't appear.

hash-mapgrouping
BuildThe busiest stretch

mins holds the minutes of each play in the log. Write busiest_stretch(mins, k) returning [start, total] for the k consecutive plays with the most minutes — ties go to the earliest start. If k is less than 1 or longer than the log, return [].

sliding-window
BossClosest repeat

Tunebox's anti-repeat rule flags a track that comes back too soon. Write closest_repeat(plays) returning [id, gap] for the two plays of the same id that are closest together in the log, where gap is the difference of their positions. If several are equally close, pick the pair whose later play comes first. No repeats → []. Aim for one pass.

hash-mapenumerate
CapstoneExactly-timed sessions

mins holds the minutes of each play in order. Write exact_sessions(mins, target) returning how many runs of consecutive plays add up to exactly target minutes. Every duration is positive. Aim for O(n) — no nested loops.

prefix-sumhash-map
library_dashboard.py
Call the function you wrote — just like your app's frontend would. Edit the inputs and hit Call.
library_dashboard(tracks, plays, budget) → dictThe library summary: size, plays, minutes, leaders and the longest session within budget.
try:
returned
Press Run to execute your code.
Press Check to run the tests for this lesson.