Problem overview
A playlist keeps songs in the order they were added. You build it from two classes that work together:
Song(title, seconds)holds one song's title and length in seconds.Playlist()starts with no songs and has four methods:add(song)adds aSongat the end.remove(title)removes the first song with that title and returnstrue. If no song has that title, it changes nothing and returnsfalse.totalSeconds()returns the sum of the lengths (0 for an empty playlist).duration()returns the same total as"m:ss": the minutes, a colon, and 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.
In the first example, a playlist gets Song("Intro", 65) and Song("Sunrise", 200). totalSeconds() returns 265 and duration() returns 4:25, since 265 seconds are 4 minutes (240 seconds) and 25 seconds. After remove("Intro") returns true, only 200 seconds are left and duration() returns 3:20. The lesson's tests print booleans as True and False.
Approach: a class that owns a list
Song is plain data: two public fields set by a constructor. Playlist owns a List<Song> in a private field, so only its own methods can change the list. The rest of the program has to go through add and remove, and the numbers that totalSeconds() and duration() report can't get out of step with the real songs.
The methods each need a short loop over the songs:
removemust work by position, so it uses an index loop,songs.get(i)andsongs.remove(i).totalSecondsonly reads every song, so it uses a for-each loop and a running total.durationdoesn't loop at all: it callstotalSeconds()and formats the result.
Step by step
- Song's fields and constructor. Declare
public String title;andpublic int seconds;, then store the parameters withthis.title = title;andthis.seconds = seconds;. - The playlist's list. Declare
private List<Song> songs = new ArrayList<>();. A new playlist starts empty. add. Add the song to the end of the list. The list keeps theSongobject itself.remove. Loop with an indexifrom 0 tosongs.size() - 1. Whensongs.get(i).title.equals(title), callsongs.remove(i)and returntrueright away. After the loop, returnfalse.totalSeconds. Starttotalat 0, add eachsong.seconds, and return the total.duration. Get the total, thenminutes = total / 60andseconds = total % 60. Ifsecondsis below 10, returnminutes + ":0" + seconds. Otherwise returnminutes + ":" + seconds.
The solution in Java
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 ArrayList<>();
public void add(Song song) {
songs.add(song);
}
public boolean remove(String title) {
for (int i = 0; i < songs.size(); i++) {
if (songs.get(i).title.equals(title)) {
songs.remove(i);
return true;
}
}
return false;
}
public int totalSeconds() {
int total = 0;
for (Song song : 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
- Compare titles with
equals.==on strings compares references and can be false for two titles with the same letters.equalscompares the characters, so"Rain"and"rain"are different titles. remove(i)removes by position. On aList<Song>,remove(int)takes out the element at that index, and the later elements move up by one place. That is safe here because the method returns right after, and never uses the shifted index.- Returning early removes only the first match. With two songs called
Echo, the firstremove("Echo")takes the first one and stops. - Strings and
+. Inminutes + ":0" + seconds, the first operand is anintbut the second is aString, so+joins the pieces as text. The result is"0:05"for 5 seconds. - Integer division and remainder.
total / 60is the whole minutes andtotal % 60is what is left. For 265 seconds, that is 4 and 25.
Using the classes
Two songs share the title Echo. Each remove takes out the first one it finds, so the duplicates are removed one at a time, in the order they were added:
Playlist playlist = new Playlist();
playlist.add(new Song("Echo", 30));
playlist.add(new Song("Echo", 45));
playlist.add(new Song("Rain", 5));
System.out.println(playlist.remove("Echo"));
System.out.println(playlist.totalSeconds());
System.out.println(playlist.remove("Echo"));
System.out.println(playlist.remove("Echo"));
System.out.println(playlist.duration());
true
50
true
false
0:05
The first call removes the 30-second song, leaving 45 + 5 = 50 seconds. The second removes the 45-second song, and the third finds no Echo, so it returns false and changes nothing. Only Rain is left, and 5 seconds are written 0:05. In a file of your own, this code also needs import java.util.*;.
Complexity
With n songs, add takes constant time, remove takes O(n) (a search, then a shift of the later songs), totalSeconds takes O(n), and duration takes O(n) because it calls totalSeconds. The playlist uses O(n) space for its songs. With at most 200 operations, none of this matters for speed.
Testing
Run on small playlists, the methods return:
| Calls | Results |
|---|---|
add Intro 65, add Sunrise 200, totalSeconds(), duration(), remove("Intro"), duration() | 265, 4:25, true, 3:20 |
On an empty playlist: duration(), remove("Rain"), totalSeconds() | 0:00, false, 0 |
add Echo 30, add Echo 45, add Rain 5, then remove("Echo"), totalSeconds(), remove("Echo"), remove("Echo"), duration() | true, 50, true, false, 0:05 |
One song of 750 seconds: duration() | 12:30 |
One song of 3600 seconds: duration() | 60:00 |
Rain 5, then remove("rain") and duration() | false, 0:05 |
Common mistakes
- Forgetting the leading zero. Returning
minutes + ":" + secondsfor every case prints0:5for 5 seconds. - Padding the minutes. 3600 seconds are
60:00, not1:00:00, and 5 seconds are0:05, not00:05. - Not returning after the removal. If the loop keeps going after
songs.remove(i), it removes every song with that title and skips the one that moves into indexi. - Returning
trueorfalsetoo early.return falsebelongs after the loop. Inside the loop it would stop at the first song that doesn't match. - Using
==for titles. Useequals. - Keeping a separate total. A running total field can drift out of step with the list after a removal. Computing the total from the list each time is simpler and always right.
