Playlist Manager
Problem Statement
Write a Java program that simulates a music playlist using a singly linked list. Each node in the list represents a song with a string name. The program should allow users to add songs (insert at the head or tail of the list), remove songs (delete by value, removing the first occurrence), and print the playlist. Test the implementation with various sequences of operations, including adding and removing songs, handling empty playlists, and dealing with duplicate song names. You can visualize this as managing a playlist where songs are added to the beginning or end, removed by name, and displayed as a sequence of song titles.
Input:
- A sequence of operations, where each operation is:
- Add at head: Insert a song name at the start of the playlist.
- Add at tail: Insert a song name at the end of the playlist.
- Remove: Delete the first occurrence of a song by name.
- Print: Display the current playlist. Output: For each operation, print the action performed (e.g., "Added song at head", "Removed song", or the playlist as a string). If a song to remove is not found, print an appropriate message. Constraints:
- The playlist size is between 0 and 10^5.
- Song names are non-empty strings of length up to 100 characters.
- The playlist may be empty or contain duplicate song names. Example:
- Input: Operations = [addHead("Song1"), addTail("Song2"), addHead("Song3"), print, remove("Song2"), print]
- Output:
Added Song1 at head Added Song2 at tail Added Song3 at head Playlist: Song3 Song1 Song2 Removed Song2 Playlist: Song3 Song1 - Input: Operations = [remove("Song1"), print]
- Output:
Song Song1 not found Playlist: []
Pseudocode
CLASS Node
SET song to string
SET next to Node (null by default)
ENDCLASS
CLASS PlaylistManager
SET head to null
FUNCTION addHead(song)
CREATE newNode as new Node(song)
SET newNode.next to head
SET head to newNode
ENDFUNCTION
FUNCTION addTail(song)
CREATE newNode as new Node(song)
IF head is null THEN
SET head to newNode
RETURN
ENDIF
SET current to head
WHILE current.next is not null
SET current to current.next
ENDWHILE
SET current.next to newNode
ENDFUNCTION
FUNCTION removeSong(song)
IF head is null THEN
RETURN false
ENDIF
IF head.song equals song THEN
SET head to head.next
RETURN true
ENDIF
SET current to head
WHILE current.next is not null
IF current.next.song equals song THEN
SET current.next to current.next.next
RETURN true
ENDIF
SET current to current.next
ENDWHILE
RETURN false
ENDFUNCTION
FUNCTION toString()
IF head is null THEN
RETURN "[]"
ENDIF
CREATE result as new StringBuilder
SET current to head
WHILE current is not null
APPEND current.song to result
IF current.next is not null THEN
APPEND " " to result
ENDIF
SET current to current.next
ENDWHILE
RETURN result as string
ENDFUNCTION
ENDCLASS
FUNCTION testPlaylist(operations)
CREATE playlist as new PlaylistManager
FOR each operation in operations
IF operation.type equals "addHead" THEN
CALL playlist.addHead(operation.song)
PRINT added song at head
ELSE IF operation.type equals "addTail" THEN
CALL playlist.addTail(operation.song)
PRINT added song at tail
ELSE IF operation.type equals "remove" THEN
IF playlist.removeSong(operation.song) THEN
PRINT removed song
ELSE
PRINT song not found
ENDIF
ELSE IF operation.type equals "print" THEN
PRINT playlist using toString
ENDIF
ENDFOR
ENDFUNCTION
FUNCTION main()
SET testCases to array of operation sequences
FOR each testCase in testCases
PRINT test case details
CALL testPlaylist(testCase)
ENDFOR
ENDFUNCTION
Algorithm Steps
- Define a
Nodeclass with: a. A stringsongfor the song name. b. Anextpointer to the next node. - Define a
PlaylistManagerclass with: a.addHead: Insert a new node with the song at the start. b.addTail: Traverse to the end and append a new node. c.removeSong: Remove the first node with the given song name, return true if found. d.toString: Convert the playlist to a space-separated string. - In
testPlaylist: a. Create aPlaylistManager. b. For each operation:- Add at head: Call
addHead, print action. - Add at tail: Call
addTail, print action. - Remove: Call
removeSong, print success or failure. - Print: Call
toString, print playlist.
- Add at head: Call
- In
main, test with sequences including adds, removes, empty playlists, and duplicates.
Java Implementation
import java.util.*;
public class PlaylistManager {
// Node class for the linked list
static class Node {
String song;
Node next;
Node(String song) {
this.song = song;
this.next = null;
}
}
// PlaylistManager class to manage songs
static class Playlist {
private Node head;
public void addHead(String song) {
Node newNode = new Node(song);
newNode.next = head;
head = newNode;
}
public void addTail(String song) {
Node newNode = new Node(song);
if (head == null) {
head = newNode;
return;
}
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
public boolean removeSong(String song) {
if (head == null) {
return false;
}
if (head.song.equals(song)) {
head = head.next;
return true;
}
Node current = head;
while (current.next != null) {
if (current.next.song.equals(song)) {
current.next = current.next.next;
return true;
}
current = current.next;
}
return false;
}
public String toString() {
if (head == null) {
return "[]";
}
StringBuilder result = new StringBuilder();
Node current = head;
while (current != null) {
result.append(current.song);
if (current.next != null) {
result.append(" ");
}
current = current.next;
}
return result.toString();
}
}
// Helper class for operations
static class Operation {
String type;
String song;
Operation(String type, String song) {
this.type = type;
this.song = song;
}
}
// Tests playlist operations
public void testPlaylist(List<Operation> operations) {
Playlist playlist = new Playlist();
for (Operation op : operations) {
if (op.type.equals("addHead")) {
playlist.addHead(op.song);
System.out.println("Added " + op.song + " at head");
} else if (op.type.equals("addTail")) {
playlist.addTail(op.song);
System.out.println("Added " + op.song + " at tail");
} else if (op.type.equals("remove")) {
if (playlist.removeSong(op.song)) {
System.out.println("Removed " + op.song);
} else {
System.out.println("Song " + op.song + " not found");
}
} else if (op.type.equals("print")) {
System.out.println("Playlist: " + playlist.toString());
}
}
}
// Main method to test playlist manager
public static void main(String[] args) {
PlaylistManager manager = new PlaylistManager();
// Test cases
List<List<Operation>> testCases = new ArrayList<>();
// Test case 1: Normal operations
testCases.add(Arrays.asList(
new Operation("addHead", "Song1"),
new Operation("addTail", "Song2"),
new Operation("addHead", "Song3"),
new Operation("print", null),
new Operation("remove", "Song2"),
new Operation("print", null)
));
// Test case 2: Empty playlist
testCases.add(Arrays.asList(
new Operation("remove", "Song1"),
new Operation("print", null)
));
// Test case 3: Single song
testCases.add(Arrays.asList(
new Operation("addHead", "Song1"),
new Operation("print", null),
new Operation("remove", "Song1"),
new Operation("print", null)
));
// Test case 4: Duplicates
testCases.add(Arrays.asList(
new Operation("addHead", "Song1"),
new Operation("addTail", "Song1"),
new Operation("addHead", "Song2"),
new Operation("print", null),
new Operation("remove", "Song1"),
new Operation("print", null)
));
// Run test cases
for (int i = 0; i < testCases.size(); i++) {
System.out.println("Test case " + (i + 1) + ":");
manager.testPlaylist(testCases.get(i));
System.out.println();
}
}
}
Output
Running the main method produces:
Test case 1:
Added Song1 at head
Added Song2 at tail
Added Song3 at head
Playlist: Song3 Song1 Song2
Removed Song2
Playlist: Song3 Song1
Test case 2:
Song Song1 not found
Playlist: []
Test case 3:
Added Song1 at head
Playlist: Song1
Removed Song1
Playlist: []
Test case 4:
Added Song1 at head
Added Song1 at tail
Added Song2 at head
Playlist: Song2 Song1 Song1
Removed Song1
Playlist: Song2 Song1
Explanation:
- Test case 1: Adds Song1 (head), Song2 (tail), Song3 (head), prints "Song3 Song1 Song2", removes Song2, prints "Song3 Song1".
- Test case 2: Attempts to remove Song1 from empty playlist, prints not found, prints "[]".
- Test case 3: Adds Song1, prints "Song1", removes Song1, prints "[]".
- Test case 4: Adds Song1 (head), Song1 (tail), Song2 (head), prints "Song2 Song1 Song1", removes first Song1, prints "Song2 Song1".
How It Works
- Node: Stores a string song name and a next pointer.
- Playlist:
addHead: Inserts new node at the start in O(1).addTail: Traverses to end, appends node in O(n).removeSong: Removes first occurrence of song, returns true if found, O(n).toString: Returns space-separated song names, "[]" for empty.
- testPlaylist: Executes operations, printing actions and results.
- Example Trace (Test case 1):
- addHead("Song1"): head=Song1→null.
- addTail("Song2"): head=Song1→Song2.
- addHead("Song3"): head=Song3→Song1→Song2.
- print: "Song3 Song1 Song2".
- remove("Song2"): head=Song3→Song1.
- print: "Song3 Song1".
- Main Method: Tests normal operations, empty playlist, single song, and duplicates.
Complexity Analysis Table
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Add Head | O(1) | O(1) |
| Add Tail | O(n) | O(1) |
| Remove Song | O(n) | O(1) |
| To String | O(n) | O(n) |
Note:
- n is the number of nodes in the playlist.
- Time complexity: O(1) for addHead; O(n) for addTail and removeSong (traverse list); O(n) for toString (traverse list).
- Space complexity: O(1) for operations (constant pointers); O(n) for toString (StringBuilder).
- Worst case: O(n) time, O(n) space for output with large playlists.
✅ Tip: Use a linked list for flexible playlist management, with head insertion for quick additions and tail insertion for appending. Test with duplicates to ensure correct removal of the first occurrence.
⚠ Warning: Handle empty playlist cases to avoid null pointer exceptions. Ensure removal checks all nodes to find the target song.