Start learning free

LESSON 568 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Playlist in TypeScript: Two Classes Working Together

Practice O(n) Read the full explanation

A music app keeps playlists: songs in the order they were added. Build it from two classes that work together. A Song holds one song's data. A Playlist keeps a list of Song objects and answers questions about them.

Implement:

  • Song(title, seconds) holds a song's title and its length in seconds;
  • Playlist() starts with no songs. Its methods:
    • add(song) adds a Song at the end of the playlist;
    • remove(title) removes the first song with that title and returns true; if no song has that title, it changes nothing and returns false;
    • totalSeconds() returns the sum of the songs' lengths in seconds (0 for an empty playlist);
    • duration() returns the same total as a string "m:ss": the minutes, a colon, then the seconds with two digits. 5 seconds are "0:05", 750 seconds are "12:30", and 3600 seconds are "60:00": the minutes are never padded.

The starter code spells every name the way your language does.

The input is a list of operations, applied in order to one new playlist. The output lists what the remove, totalSeconds and duration calls return, in order, with booleans printed as True and False.

 

Example 1:

Input:
    add(Song("Intro", 65))
    add(Song("Sunrise", 200))
    totalSeconds()
    duration()
    remove("Intro")
    duration()
Output: 265 4:25 True 3:20
Explanation: The two songs last 65 + 200 = 265 seconds: 4 minutes (240 seconds) and 25 seconds.
After "Intro" is removed, only the 200 seconds of "Sunrise" are left: 3 minutes and 20 seconds.

Example 2:

Input:
    duration()
    remove("Rain")
    totalSeconds()
Output: 0:00 False 0
Explanation: The playlist is empty: it lasts 0 seconds, and there is no "Rain" to remove.

Example 3:

Input:
    add(Song("Echo", 30))
    add(Song("Echo", 45))
    add(Song("Rain", 5))
    remove("Echo")
    totalSeconds()
    remove("Echo")
    remove("Echo")
    duration()
Output: True 50 True False 0:05
Explanation: Two songs are called "Echo". The first remove takes only the first one, the 30 seconds,
so 45 + 5 = 50 seconds are left. The second remove takes the other "Echo", and the third finds none.
Only "Rain" is left: 5 seconds, written 0:05.

 

Constraints:

  • 1 <= seconds <= 3600
  • A title is one word of letters and digits. Titles are compared exactly: "Rain" and "rain" are different titles.
  • At most 200 operations.
STEP 1 OF 16
    Ctrl + Enter to check

    TYPESCRIPT · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Playlist in TypeScript, explained

    Problem overview

    A music app keeps playlists: songs in the order they were added. You build it from two classes. A Song holds one song's title and its length in seconds. A Playlist starts empty, keeps a list of Song objects and has four methods:

    • add(song) puts a song at the end;
    • remove(title) removes the first song with that title and returns true, or changes nothing and returns false;
    • totalSeconds() returns the sum of the lengths, 0 for an empty playlist;
    • duration() returns the same total as "m:ss": minutes (never padded), a colon and two digits of seconds. 5 seconds are "0:05", 750 are "12:30" and 3600 are "60:00".

    Two songs of 65 and 200 seconds make 265 seconds, which is 4 minutes (240 seconds) and 25 seconds: "4:25".

    Approach: objects that hold objects

    The playlist keeps whole Song objects in an array, so each song's title and length stay together. The array is private: only the playlist's own methods change it, so what totalSeconds and duration report can't get out of step with the list.

    remove works by position, because the array's splice removes by index. A for loop with an index finds the first matching title, removes that song and returns true right away. Returning at once stops the loop, so only the first match goes. If the loop ends without a match, the method returns false. duration doesn't add anything itself; it calls totalSeconds() and only formats the result. Adding up the lengths takes O(n) time for n songs, and so does remove, which may walk the whole list. add takes constant time.

    Step by step

    1. Song fields and constructor. Declare title: string and seconds: number, and store both parameters in this. They stay public because the playlist reads them.
    2. The list. In Playlist, declare private songs: Song[] = []. Every new playlist gets its own empty array.
    3. add. Push the song onto this.songs.
    4. remove. Loop with let i from 0 while i < this.songs.length. Inside, compare this.songs[i].title with title using ===. On a match, this.songs.splice(i, 1) removes that one element, the later songs move up one place, and the method returns true. After the loop, return false.
    5. totalSeconds. Start total at 0, add each song.seconds in a for...of loop and return it.
    6. duration, part one. Store this.totalSeconds() in a const total. Minutes are Math.floor(total / 60) and seconds are total % 60.
    7. duration, part two. When the seconds are below 10, return the minutes, ":0" and the seconds. Otherwise return the minutes, ":" and the seconds.

    The solution in TypeScript

    class Song {
        title: string;
        seconds: number;
    
        constructor(title: string, seconds: number) {
            this.title = title;
            this.seconds = seconds;
        }
    }
    
    class Playlist {
        private songs: Song[] = [];
    
        add(song: Song): void {
            this.songs.push(song);
        }
    
        remove(title: string): boolean {
            for (let i = 0; i < this.songs.length; i++) {
                if (this.songs[i].title === title) {
                    this.songs.splice(i, 1);
                    return true;
                }
            }
            return false;
        }
    
        totalSeconds(): number {
            let total = 0;
            for (const song of this.songs) {
                total += song.seconds;
            }
            return total;
        }
    
        duration(): string {
            const total = this.totalSeconds();
            const minutes = Math.floor(total / 60);
            const seconds = total % 60;
            if (seconds < 10) {
                return minutes + ":0" + seconds;
            }
            return minutes + ":" + seconds;
        }
    }
    

    TypeScript details worth knowing:

    • A field with an initial value. private songs: Song[] = []; runs for each new Playlist(), so two playlists never share a list.
    • splice(i, 1) changes the array. It removes one element starting at index i and shifts the later ones down by one place. That is why remove returns right after calling it: the indexes have just changed, and the loop shouldn't continue.
    • / keeps the fraction. 265 / 60 is about 4.4167, so Math.floor keeps the whole minutes. % gives what's left over.
    • Numbers join strings with +. minutes + ":0" + seconds starts with a number, but once ":0" is in the sum, the rest is joined as text. The result is a string, as duration() promises.
    • === compares exactly. "Rain" and "rain" are different titles.

    Seeing it run

    const p = new Playlist();
    p.add(new Song("Intro", 65));
    p.add(new Song("Sunrise", 200));
    console.log(p.totalSeconds(), p.duration(), p.remove("Intro"), p.duration());
    
    const q = new Playlist();
    console.log(q.duration(), q.remove("Rain"), q.totalSeconds());
    
    const r = new Playlist();
    r.add(new Song("Echo", 30));
    r.add(new Song("Echo", 45));
    r.add(new Song("Rain", 5));
    console.log(r.remove("Echo"), r.totalSeconds(), r.remove("Echo"), r.remove("Echo"), r.duration());
    
    const t = new Playlist();
    t.add(new Song("A", 750));
    console.log(t.duration());
    t.add(new Song("B", 2850));
    console.log(t.duration(), t.totalSeconds());
    console.log(r.remove("rain"));
    265 4:25 true 3:20
    0:00 false 0
    true 50 true false 0:05
    12:30
    60:00 3600
    false

    The empty playlist lasts 0:00, and there is no Rain to remove. In the third playlist two songs are called Echo. The first remove takes only the first one (30 seconds), which leaves 45 + 5 = 50 seconds. The second takes the other Echo, and the third finds none, so only Rain is left: 5 seconds, written 0:05. A 750-second song is 12:30, and adding 2850 seconds more makes exactly 3600, which is 60:00: the minutes aren't capped at 59 and never padded. The last line shows that "rain" doesn't match "Rain".

    Edge cases and common mistakes

    • Removing every match. Without the return true inside the loop, the loop would keep going after splice and skip the song that just moved into index i. Return right away to remove only the first.
    • Forgetting the padding. Without the ":0" branch, 5 seconds read 0:5. The branch is for seconds below 10 only.
    • Padding the minutes. Don't: "60:00" and "0:05" keep the minutes as they are.
    • Empty playlist. totalSeconds() starts at 0, so it returns 0 and duration() returns "0:00". remove on an empty list returns false.
    • Adding in duration. Calling this.totalSeconds() keeps the adding in one place; copy it and the two methods can drift apart.
    • Making songs public. Outside code could then change the list without going through add and remove. private is a compile-time check in TypeScript, and the compiler rejects access from outside the class.
    • Wrong comparison. A single = assigns instead of comparing. Use ===.
    Keep going with TypeScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free