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 data, its title and its length in seconds. A Playlist keeps a vector of Song objects and answers questions about them:
add(song)puts a song at the end;remove(title)removes the first song with that title and returnstrue, or changes nothing and returnsfalsewhen there is none;totalSeconds()returns the sum of the lengths, 0 for an empty playlist;duration()returns the same total as a string"m:ss": minutes, a colon, and the seconds with two digits.
In the first example the playlist gets Intro (65 seconds) and Sunrise (200). The total is 265 seconds, which is 4 minutes (240 seconds) and 25 seconds, so duration() is 4:25. After remove("Intro") returns true, only 200 seconds are left, and the duration is 3:20.
Approach: one class holds the data, the other holds the list
Splitting the work this way keeps each class simple. Song has nothing to do but remember two values. Playlist keeps its songs in a private vector, so only its own methods can change the list, and the numbers it reports always match what is inside.
The methods then follow from simple ideas:
- remove walks the vector by index. At the first match, it erases that element and returns
truestraight away, which is why only the first song with the title goes. If the loop ends without a match, the answer isfalse. - totalSeconds adds up every song's
seconds. - duration reuses
totalSecondsand formats the result. The minutes are the total divided by 60, using whole-number division. The seconds are the remainder,total % 60.
Step by step
- In
Song, declarestring title;andint seconds;underpublic:, and store the constructor's parameters withthis->. They are public becausePlaylistreads them. - In
Playlist, declarevector<Song> songs;underprivate:. - In
add, callsongs.push_back(song). - In
remove, write aforloop with an indexifrom 0 up tosongs.size(). Ifsongs[i].title == title, erase the element withsongs.erase(songs.begin() + i)and returntrue. After the loop, returnfalse. - In
totalSeconds, keep atotalstarting at 0, addsong.secondsfor each song and return it. - In
duration, storetotalSeconds(), then computeminutesandseconds. Ifsecondsis below 10, return the minutes,":0"and the seconds. Otherwise return the minutes,":"and the seconds.
The solution in C++
class Song {
public:
string title;
int seconds;
Song(string title, int seconds) {
this->title = title;
this->seconds = seconds;
}
};
class Playlist {
private:
vector<Song> songs;
public:
void add(Song song) {
songs.push_back(song);
}
bool remove(string title) {
for (int i = 0; i < songs.size(); i++) {
if (songs[i].title == title) {
songs.erase(songs.begin() + i);
return true;
}
}
return false;
}
int totalSeconds() {
int total = 0;
for (Song& song : songs) {
total += song.seconds;
}
return total;
}
string duration() {
int total = totalSeconds();
int minutes = total / 60;
int seconds = total % 60;
if (seconds < 10) {
return to_string(minutes) + ":0" + to_string(seconds);
}
return to_string(minutes) + ":" + to_string(seconds);
}
};
A run
Take a playlist with Echo (30), Echo (45) and Rain (5), then call remove("Echo"), totalSeconds(), remove("Echo"), remove("Echo") and duration(). The results are:
True 50 True False 0:05
The first remove takes only the first Echo, so 45 + 5 = 50 seconds are left. The second takes the other one, and the third finds none. Only Rain remains: 5 seconds, written 0:05.
For an empty playlist, duration() is 0:00, remove("Rain") is false and totalSeconds() is 0. Single songs of 5, 750 and 3600 seconds give 0:05, 12:30 and 60:00; 59, 60 and 61 seconds give 0:59, 1:00 and 1:01.
Details that matter
erasetakes a position, not an index.songs.begin() + iis the position of elementi. After the erase, the elements after it move up one place.- Return right after the erase. The loop changes the vector it is walking through. Stopping at once is both what the task asks (only the first match goes) and the safe thing to do.
to_stringturns anintinto astring, so the pieces can be joined with+.- Minutes are never padded. Only the seconds get a leading zero, so 3600 seconds is
60:00, not01:00:00. - Signed index and
size().songs.size()is unsigned, and the loop compares it with anint. That is fine for these small sizes. Be careful with expressions likesongs.size() - 1: on an empty vector the unsigned subtraction wraps around, and the result is 18446744073709551615.
Complexity
For n songs, add is O(1) on average. remove is O(n): the loop may look at every song, and the erase moves the elements after the removed one. totalSeconds is O(n), and so is duration, since it calls totalSeconds. The extra space is O(1) apart from the stored songs.
Edge cases and common mistakes
- Not padding the seconds. Dropping the
":0"branch gives0:5for 5 seconds,60:0for 3600 and1:0for 60. Every single-digit seconds value needs the zero, including 0 itself. - Removing every match. Only the first song with the title goes. Two songs can share a title.
- Matching titles loosely.
Rainandrainare different titles, and==on strings compares them exactly. - Forgetting
return false. A non-void function that reaches its closing brace without returning a value is undefined behaviour in C++. Thereturn false;after the loop covers the case of no match. - Empty playlist. The total is 0, the duration is
0:00, andremovereturns false.
Testing
| Operations | Results |
|---|---|
| add Intro 65, add Sunrise 200, totalSeconds, duration, remove Intro, duration | 265 4:25 True 3:20 |
| duration, remove Rain, totalSeconds (empty playlist) | 0:00 False 0 |
| add Echo 30, add Echo 45, add Rain 5, remove Echo, totalSeconds, remove Echo, remove Echo, duration | True 50 True False 0:05 |
