Playlist
Model a music playlist using tuples and a variant type for genre. You will practice tuple destructuring, list operations, and higher-order functions to filter, format, and summarize a collection of tracks.
A track is a tuple of title, artist, duration in seconds, and genre. Build a library of functions that work with a list of such tracks: compute total duration, filter by genre or artist, extract titles, and format tracks for display.
- 01Define a
genrevariant type withRock,Jazz,Classical, andPopconstructors. - 02Create at least 5 track tuples of type
string * string * int * genreand collect them in a list. - 03Write
durationthat takes a track and returns its duration in seconds. - 04Write
total_durationthat takes a track list and returns the sum of all durations using recursion. - 05Write
filter_by_genreandfilter_by_artistusingList.filter. - 06Write
track_titlesusingList.mapto extract just the title from each track. - 07Write
long_tracksthat returns only tracks longer than a given threshold in seconds. - 08Write
format_trackthat returns a formatted string, for example"Bohemian Rhapsody by Queen (5:54)".
Starter code
type genre = Rock | Jazz | Classical | Pop (* A track: (title, artist, duration_sec, genre) *) let track1 = ("Bohemian Rhapsody", "Queen", 354, Rock) let track2 = ("So What", "Miles Davis", 562, Jazz) (* add more tracks... *) let playlist = [track1; track2 (* ... *)] let duration track = (* TODO: extract the third element *) 0 let rec total_duration = function | [] -> 0 | track :: rest -> (* TODO *) 0 let filter_by_genre g tracks = List.filter (fun _ -> (* TODO *) false) tracks let track_titles tracks = List.map (fun _ -> (* TODO *) "") tracks let format_track (title, artist, d, _) = (* TODO: use Printf.sprintf minutes = d / 60, seconds = d mod 60 *) ""
Hints
For duration, use a wildcard pattern to destructure the tuple: let duration (_, _, d, _) = d. The same approach works in any function that only needs certain fields.
For filter_by_genre, the lambda needs to extract the fourth element and compare it to g. Notice that filter_by_artist follows the exact same pattern, just extracting the second element instead. This repetition hints that partial application is at work: both functions are specializations of a more general filtering idea.
For format_track, use Printf.sprintf "%s by %s (%d:%02d)". The %02d format pads the seconds with a leading zero when needed.
filter_by_genre and filter_by_artist are structurally identical. The only thing that changes is which field is compared and to what. This is the same idea as partial application: parameterize the behavior instead of duplicating it.
type genre = Rock | Jazz | Classical | Pop let track1 = ("Bohemian Rhapsody", "Queen", 354, Rock) let track2 = ("So What", "Miles Davis", 562, Jazz) let track3 = ("Moonlight Sonata", "Beethoven", 375, Classical) let track4 = ("Billie Jean", "Michael Jackson", 294, Pop) let track5 = ("Comfortably Numb", "Pink Floyd", 382, Rock) let playlist = [track1; track2; track3; track4; track5] let duration (_, _, d, _) = d let rec total_duration = function | [] -> 0 | track :: rest -> duration track + total_duration rest let filter_by_genre g tracks = List.filter (fun (_, _, _, tg) -> tg = g) tracks let filter_by_artist artist tracks = List.filter (fun (_, a, _, _) -> a = artist) tracks let track_titles tracks = List.map (fun (title, _, _, _) -> title) tracks let long_tracks min_sec tracks = List.filter (fun track -> duration track > min_sec) tracks let format_track (title, artist, d, _) = Printf.sprintf "%s by %s (%d:%02d)" title artist (d / 60) (d mod 60) let print_playlist tracks = List.iter (fun t -> print_endline (format_track t)) tracks let () = Printf.printf "Total: %d:%02d\n" (total_duration playlist / 60) (total_duration playlist mod 60); print_endline "Rock tracks:"; print_playlist (filter_by_genre Rock playlist); print_endline "Long tracks (5+ min):"; print_playlist (long_tracks 300 playlist)
Walkthrough
Tuple destructuring. The wildcard pattern (_, _, d, _) unpacks only what is needed and discards the rest. This is the idiomatic way to access fields in a tuple: no index syntax and no boilerplate.
filter_by_genre vs filter_by_artist. Both are one-liners that differ only in which field they compare. This similarity is intentional. In a real codebase you might abstract them into a single filter_by_field function that accepts a field extractor, but naming them explicitly is clearer here.
format_track. The %02d format specifier pads the value with a leading zero so that 5 minutes and 4 seconds prints as 5:04 instead of 5:4. This is the same as the C printf format, which OCaml inherits.
print_playlist. List.iter applies a function to every element for its side effects. Unlike List.map, it returns unit. Using it here keeps the printing logic separate from the formatting logic.
Going further
Once the basic version works, try these extensions:
- Write
sort_by_durationusingList.sortwith a custom comparator. - Group tracks by genre into a list of
(genre * track list)pairs. - Write
shuffleusingList.sortwithRandom.intas the comparator.