Start learning free

LESSON 567 OF 572 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Playlist in C#: 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

    C# · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Playlist in C#, explained

    Problem overview

    A music app keeps playlists: songs in the order they were added. You build it from two classes. A Song holds a title and its length in seconds. A Playlist 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, a colon, and seconds with two digits.

    After adding Intro (65 seconds) and Sunrise (200 seconds), TotalSeconds() is 265 and Duration() is "4:25".

    Approach: one class holds the data, the other uses it

    Song is plain data: two public fields and a constructor. Playlist keeps its songs in a private List<Song>. Because the list is private, only the playlist's own methods can change it, so what TotalSeconds and Duration report always matches what Add and Remove did.

    Every method is a small loop over that list. Duration reuses TotalSeconds, so the adding is written only once.

    Step by step

    1. In Song, declare public string title and public int seconds and store the constructor's parameters with this..
    2. In Playlist, declare private List<Song> songs = new List<Song>();. A new playlist starts empty.
    3. Add calls songs.Add(song).
    4. Remove needs positions, so it uses a for loop over i from 0 to songs.Count. When songs[i].title == title, it calls songs.RemoveAt(i) and returns true at once. Returning stops the loop, so only the first match goes. After the loop, it returns false.
    5. TotalSeconds starts total at 0 and adds song.seconds for each song in a foreach.
    6. Duration splits the total: total / 60 whole minutes and total % 60 leftover seconds. If the seconds are below 10 it inserts a 0 after the colon; otherwise it does not.

    The solution in C#

    class Song {
        public string title;
        public int seconds;
    
        public Song(string title, int seconds) {
            this.title = title;
            this.seconds = seconds;
        }
    }
    
    class Playlist {
        private List<Song> songs = new List<Song>();
    
        public void Add(Song song) {
            songs.Add(song);
        }
    
        public bool Remove(string title) {
            for (int i = 0; i < songs.Count; i++) {
                if (songs[i].title == title) {
                    songs.RemoveAt(i);
                    return true;
                }
            }
            return false;
        }
    
        public int TotalSeconds() {
            int total = 0;
            foreach (Song song in songs) {
                total += song.seconds;
            }
            return total;
        }
    
        public string Duration() {
            int total = TotalSeconds();
            int minutes = total / 60;
            int seconds = total % 60;
            if (seconds < 10) {
                return minutes + ":0" + seconds;
            }
            return minutes + ":" + seconds;
        }
    }

    Details worth knowing

    The two-digit seconds. 5 seconds must read 0:05, not 0:5, so a seconds value under 10 gets a ":0". The minutes are never padded: 750 seconds print as 12:30 and 3600 seconds as 60:00. A playlist of 55 + 5 seconds prints 1:00.

    Strings and numbers with +. In minutes + ":0" + seconds, the + between the number and the text joins them as text, so the integers appear as digits.

    Removing while looping. RemoveAt(i) shifts the later songs one place up. That is safe here because the method returns immediately after removing, so the loop never continues with shifted positions.

    Exact titles. The comparison with == on strings compares their contents, and it is case-sensitive. On a playlist holding one song titled "Rain", Remove("rain") returns False and changes nothing, while Remove("Rain") returns True. Titles that differ only in case are different titles, which is why a title is compared exactly and never converted to lower case first.

    Testing

    Example 3 as code:

    var playlist = new Playlist();
    playlist.Add(new Song("Echo", 30));
    playlist.Add(new Song("Echo", 45));
    playlist.Add(new Song("Rain", 5));
    Console.WriteLine(playlist.Remove("Echo"));
    Console.WriteLine(playlist.TotalSeconds());
    Console.WriteLine(playlist.Remove("Echo"));
    Console.WriteLine(playlist.Remove("Echo"));
    Console.WriteLine(playlist.Duration());
    True
    50
    True
    False
    0:05

    An empty playlist gives Duration() 0:00, Remove("Rain") False and TotalSeconds() 0. (When C# prints a bool it writes True and False.)

    Complexity

    Add is O(1) on average. Remove, TotalSeconds and Duration are O(n) for n songs, since each may look at every song once.

    How Duration turns seconds into text

    Integer division gives the whole minutes and % gives what is left. These came out of the code:

    TotalSeconds()total / 60total % 60Duration()
    0000:00
    5050:05
    590590:59
    60101:00
    65151:05
    2654254:25
    750123012:30
    360060060:00

    The padding matters in the rows where the leftover seconds are below 10: 0, 5 and 9 must print as 00, 05 and 09. The branch for values below 10 supplies the extra zero. Seconds from 10 to 59 already have two digits, and the minutes are printed as they are.

    Duplicates

    With songs A (10 seconds), B (20) and A (30), the first Remove("A") returns True and leaves 50 seconds, the second returns True and leaves 20, and the third returns False and still leaves 20. Each call removes at most one song.

    Common mistakes

    • Removing every matching song instead of only the first.
    • Writing minutes + ":" + seconds without the zero, giving 0:5.
    • Padding the minutes.
    • Making songs public, so code outside can change the list behind the playlist's back.

    Two fields named seconds

    Inside Duration the local variable seconds is the leftover seconds from the split, while Song has a field of the same name for a song's length. They live in different classes, so they never clash: song.seconds always means the song's length, and a bare seconds in Duration is the local variable.

    Also, TotalSeconds() is called from Duration() without any object in front of it: inside a class, a method name alone means "this object's method".

    Key takeaways

    • Two classes can divide the work: one holds a record's data, the other holds a collection of them and answers questions.
    • Keeping the collection private means only the class's own methods change it.
    • Searching by index with a for loop is the right tool when you need the position, for example to call RemoveAt.
    • Build one method on another (Duration on TotalSeconds) so each rule exists in one place.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free