$29.99
Project 3: PlayList (a better one )
So you did a great job, (CONGRATULATIONS!) and you are now enjoying your favorite music while sitting at your desk... but ... you are a perfectionist, and although your playlist is working just fine, you know you could have done something better. You want your software design and architecture to always be "just perfect"! You can't let your perfectionism haunt you for the rest of the day, so you decide to skip lunch break and have a sandwich at your desk while working on perfecting your PlayList class.
Time to put on your Design hat back on ... You remember that in your Software Analysis and Design II class your professor just discussed an new implementation for the Bag Abstract Data Type, a LinkedBag. Yes, you decide you will try to re-implement Set to use a linked chain rather than an array data structure. There is something else that bothers you about your current implementation of PlayList. Most of its methods are simply calling Set methods that implement a particular functionality that you would like your PlayList to have. Right now your PlayList "Has-a" Set private member variable. But you now realize that really your PlayList "Is-a" Set which stores Songs. So yes, you will re-design your PlayList to inherit from Set (the modified linked version of it), and you will modify some of its functionalities.
Design an Enhanced PlayList Program
Node and LinkedSet classes
You still have an interface, SetInterface.h (just like BagInterface.h but it does not allow duplicates), and you are given a new version of Set (LinkedSet.h and LinkedSet.cpp). Moreover, the LinkedSet makes use of Node objects. These files are provided as well (Node.h and Node.cpp). These files are all available on Blackboard under Course Materials/Project3 (5 files total). LinkedSet has the same functionalities as Set, in fact these derive from the same public interface SetInterface. However, their implementation is very different. Although the class is given to you, it is your responsibility to fully understand how Node and LinkedSet work. DO NOT ATTEMPT TO START CODING UNTIL YOU HAVE A STRONG UNDERSTANDING OF WHAT THESE CLASSES DO AND HOW THEY DO IT, you will just put yourself in A LOT of trouble if you do so. The first task in this project is essentially a reading assignment. READ AND UNDERSTAND THIS CODE!!! (LinkedBag is covered in lectures 6/7).
Class Song
You did a great job with Song, so you will just keep it as it is (you will use the Song.h and Song.cpp from Project 2).
Class PlayList
The bulk of the work for this project is in the redesign/reimplementation of your PlayList class. PlayList will now inherit from (is a derived class of) LinkedSet. So it will inherit all of LinkedSet's public and protected members, and these will automatically be available to PlayList through inheritance... that saves you a lot of work, pretty cool huh!?! But you do want to change a few things (name all methods and objects exactly as described below, test your methods incrementally , do not override any methods other than the ones specified below): 1. PlayList will only ever hold Songs, not some arbitrary ItemType, so it will inherit from LinkedSet<Song class PlayList : public LinkedSet<Song
2. PlayList will have a single private data member: a pointer to its last node (I will explain why below) Node<Song* tail_ptr_; // Pointer to last node
3. Recall: constructors and destructors are not inherited, so you must ALWAYS declare and implement these in a derived class. Base class constructors are called before derived class constructors, so all LinkedSet derived members will be taken care of. PlayList constructors need to make sure to correctly initialize the derived class private data members (tail_ptr_). PlayList(); //default constructor PlayList(const Song& a_song); //parameterized constructor
Wen implementing the copy constructor beware of slicing! Moreover, keep in mind that LinkedSet's copy constructor will make a deep copy of the chain, but it will only take care of copying the baseclass data members. PlayList's copy constructor will also need to correctly point tail_ptr_ to the last node in the copied chain, unless the chain is empty. To do so, you will also write a private member function getPointerToLastNode that traverses the chain and returns a pointer to the last node. The copy constructor will call this private function to correctly set the value of tail_ptr_ so that it points to the last node. Once again, the copy constructor is public while getPointerToLastNode is private PlayList(const PlayList& a_play_list); // copy constructor
Node<Song* getPointerToLastNode() const;
Derived class destructors are called before base class destructors. LinkedSet's constructor will take care of correctly deleting all the nodes thus returning memory to the system. PlayList's destructor needs to unloop()(described below) first, otherwise LinkedSet's destructor will not behave correctly if the chain is looped.
~PlayList(); // Destructor 4. A PlayList is like a Set in that you don't want to have the same song play multiple times. It would be nice, however, to add songs at the end of the PlayList instead of adding at the beginning like Set does. So you decide to override add() to do just that. Just as in Set you always have access to the first node through head_ptr_ . Conveniently, in PlayList you also always have access to the last node through its tail_ptr_private data member. As you write this function makes sure you add correctly when the PlayList is empty as well. Test both cases, and make sure both head_ptr_ and tail_ptr point to the correct node at the end of this operation.
bool add(const Song& new_song) override; 5. Another thing that bothers you about your current implementation of PlayList is that removing a Song does not retain the order in which you added Songs to your PlayList. So you will override remove() to preserve the order of the Songs.
To do this, you will implement your own private member function getPointerTo which will return a pointer to the item you want to delete. This function will also have a reference parameter previous_ptr which will be set to point to the node preceding the one you want to remove. This will be useful to retain the order in the chain. So remove will call getPointerTo and using both pointers it will be able to delete a node while preserving the order and keeping the chain connected. Keep in mind that remove is public while getPointerTo is private (a helper function)
bool remove(const Song& a_song) override;
// post: previous_ptr is null if target is not in PlayList or if target is the // first node, otherwise it points to the node preceding target // return: either a pointer to the node containing target // or the null pointer if the target is not in the PlayList. Node<Song* getPointerTo(const Song& target, Node<Song*& previous_ptr) const;
6. Another typical thing to do with PlayLists is to play them in a loop. Well, you now have both a pointer to the first node and a pointer to the last node. It is thus trivial to write a loop() and an unloop() method for your PlayList. Note: all you are doing here is looping and unlooping the chain, "playing the songs" is clearly not a concern for this project. void loop(); void unloop();
7. Finally, you still want to display the songs in your PlayList. Just as in your previous implementation, you will take advantage of the toVector member inherited from LinkedSet to display the contents of the PlayList with the same format as in your previous implementation. void displayPlayList();
Usage:
Make sure your code compiles and runs correctly with this main() function:
int main() { // Test PlayList std::cout << "**** Instantiating Song objects **** \n"; Song song1("song1","author1","album1"); Song song2("song2", "author2", "album2"); Song song3("song3", "author3", "album3"); Song song4("song4", "author4", "album4"); Song song5("song5", "author5", "album5"); std::cout << "\n\n**** Testing PlayList class **** \n"; std::cout << "\nAdd songs to PlayList, check adding to end of chain and no duplicates allowed: \n"; PlayList playlist1(song1); playlist1.add(song2); playlist1.add(song3); playlist1.add(song1); playlist1.add(song2); playlist1.add(song3);
std::vector<Song song_vector = playlist1.toVector();
playlist1.displayPlayList(); std::cout << "\n\nAdd remaining songs\n"; playlist1.add(song4); playlist1.add(song5); playlist1.displayPlayList(); std::cout << "\n\nCheck that linked chain corresponds in LinkedSet(true)\n"; std::cout << playlist1.contains(song3); // (true)
std::cout << "\n\nTest removing songs from PlayList, check that order is preserved\n"; std::cout << "\nRemove from the middle\n"; playlist1.remove(song3); playlist1.displayPlayList(); std::cout << "\nRemove first song\n"; playlist1.remove(song1); playlist1.displayPlayList(); std::cout << "\nRemove last song\n"; playlist1.remove(song5); playlist1.displayPlayList(); std::cout << "\n\nCheck that linked chain corresponds in LinkedSet after removal (false)\n"; std::cout << playlist1.contains(song3); //(false) std::cout << "\n\nTest copy constructor\n"; PlayList playlist2 = playlist1; std::cout << "\nPrinting playlist2 \n"; playlist2.displayPlayList(); std::cout << "\n\nCheck that copied chain corresponds in LinkedSet\n"; std::cout << playlist2.contains(song2) << std::endl; // (true) std::cout << playlist2.contains(song3) << std::endl; //(false)
std::cout << "\nAdd song to copied playlist\n"; playlist2.add(song1); playlist2.displayPlayList(); std::cout << "\nRemove last song from copied playlist\n"; playlist2.remove(song1); playlist2.displayPlayList(); std::cout << "\nRemove first song from copied playlist\n"; playlist2.remove(song2); playlist2.displayPlayList(); return 0; }
Output:
Running the above main() program should produce this output:
**** Instantiating Song objects ****
**** Testing PlayList class ****
Add songs to PlayList, check adding to end of chain and no duplicates allowed: * Titile: song1* Author: author1* Album: album1 * * Titile: song2* Author: author2* Album: album2 * * Titile: song3* Author: author3* Album: album3 * End of playlist
Add remaining songs * Titile: song1* Author: author1* Album: album1 * * Titile: song2* Author: author2* Album: album2 * * Titile: song3* Author: author3* Album: album3 * * Titile: song4* Author: author4* Album: album4 * * Titile: song5* Author: author5* Album: album5 * End of playlist
Check that linked chain corresponds in LinkedSet(true) 1
Test removing songs from PlayList, check that order is preserved
Remove from the middle * Titile: song1* Author: author1* Album: album1 * * Titile: song2* Author: author2* Album: album2 * * Titile: song4* Author: author4* Album: album4 * * Titile: song5* Author: author5* Album: album5 * End of playlist
Remove first song * Titile: song2* Author: author2* Album: album2 * * Titile: song4* Author: author4* Album: album4 * * Titile: song5* Author: author5* Album: album5 * End of playlist
Remove last song * Titile: song2* Author: author2* Album: album2 * * Titile: song4* Author: author4* Album: album4 * End of playlist
Check that linked chain corresponds in LinkedSet after removal (false) 0
Test copy constructor
Printing playlist2 * Titile: song2* Author: author2* Album: album2 * * Titile: song4* Author: author4* Album: album4 * End of playlist
Check that copied chain corresponds in LinkedSet 1 0
Add song to copied playlist * Titile: song2* Author: author2* Album: album2 * * Titile: song4* Author: author4* Album: album4 * * Titile: song1* Author: author1* Album: album1 * End of playlist
Remove last song from copied playlist * Titile: song2* Author: author2* Album: album2 * * Titile: song4* Author: author4* Album: album4 * End of playlist
Remove first song from copied playlist * Titile: song4* Author: author4* Album: album4 *
End of playlist
Program ended with exit code: 0
The task:
For this project you will use the same class interface SetInterface.h that LinkedSet inherits from. You are also give the Node class and the LinkedSet class (Node.h, Node.cpp, LinkedSet.h and LinkedSet.cpp). You will reuse the Song class you implemented for Project 2. You are also given as a sample usage main() function. This is the function that will be used by Gradescope to test your code. You need to write and submit only the PlayList class (2 files: PlayList.h and PlayList.cpp). The bulk of the project is the implementation of add(), remove(), the copy constructor and the two helper functions. It is important that you implement and test these one by one. Use an incremental approach to your development, and use stubs where necessary.
Submission:
Your project must be submitted on Gradescope. The due date is October 16 by NOON (12pm). Feel free to submit early!!! Multiple submissions are allowed, only the last submission will be graded. No late submissions will be accepted. For grading purposes, it is important that you name all methods and variables as prescribed by this project description. Failing to do so may result in compiler errors when you submit to Gradescope, and therefore you will loose points. Submit PlayList.h, PlayList.cpp (2 files) ONLY. Your code will be tested with the above main function for grading, so make sure your code runs without problems with the above main function on the machines in the Linux lab. All your files must include a preamble comment with the following information: file name, your name, date, assignment (e.g. CSCI 235, Fall 2018, Project 3) and a brief description. All functions should have a comment preamble including a description, as well as pre, post conditions and return when appropriate. Feel free to use the same preamble you write in the interface for each corresponding method in the implementation. A note about Gradescope: Please keep in mind that Gradescope is not a debugger. Correctness is not solely based on obtaining the correct output. The autograder does some checking of the structure of the source code as well. It will always show you the output, even if the errors are caused by a check of the source. Please make sure you follow the instructions in the project specifications exactly (public, protected and private members as well as inheritance and templates as specified). Please also conform to the design practices we have been discussing in class. Separate your interface from your implementation. Use header files for declarations only (of classes and templates as well), and separate all implementation into the .cpp files. Keep the file extensions as requested by this document. You are welcome and encouraged to explore and understand alternatives, that is the best way to learn, but do that after you have submitted and obtained full credit.
Have Fun!!!!!
Image sources:
freeiconspng.com kisspng.com ubisafe.org emojiisland.com