Project 4: Fundraiser event

UC Irvine - Fall ‘22 - ICS 45C

Read everything before you start!
Updates will be listed here:

  • added hint about operator<<;
  • added hint about reverse;
  • added hint about destructor;
  • gradescope link added;
    • please be aware that an infinite loop anywhere in your code will make all tests fail, we’re working on a solution for that;
    • infinite loop problem solved :)
  • added a precision requirement for printTop;
  • added a precision requirement for operator<<;
  • added a note saying you can add things to the linked list node.

Sections

Due date

This project is due at 10PM PT on Wednesday of week 9 (2022-11-23). You have a 15-minute buffer for any technical issues and can use your extra days if needed.

Late Submissions

As we talked about during lecture and later announced on EdStem, if you submit something on 2022-11-24, 2022-11-25, or 2022-11-26, that will count as only one extra day. Submitting something on 2022-11-27 would use 2 late days, and things get back to normal usage from that point.

However, please note that this project is due before the break for the holiday to encourage you to take that time off. Please be aware that help will be a lot more limited during the holiday break and the following weekend.

Also, this is getting close to the end of the quarter! Project 5 will be due on the last day of classes, so please make sure you plan for both.

Context

You now run a bank! Congrats… I guess? Anyway, you managed to get a few clients to open a few accounts and plan to expand your operations. You want more clients, exposure, transactions, liability, and all that good stuff. But you’re having trouble finding people to sign up. On top of that, you don’t really have the servers to handle all those operations you’re expecting soon.

Someone gave you the idea to hold a fundraiser event, so you can spread the word about your new bank and make a little extra money to help with the cost of those new servers.

That was indeed a great idea! You’re planning to have a big event where people can play all sorts of carnival games and competitions. The top performers on each event will get up to a $200 bonus on their new checking account1, but you’re not sure how to track who’s winning each event. It seems like a hard problem since, in some events, it’s better to have a lower score (e.g., mini golf), and in others, it’s better to have a higher score (e.g., 3pt shooting contest). Additionally, some competitions measure things in whole numbers (e.g., number of plates broken), while others use fractional ones (e.g., 5k time). Luckily, you remembered that you can use templates to account for different types!

What you have to do

You will implement a template class called FundraiserRank. Your class should work for different types the user wants and provide the methods described below on this page. This class should be in the ICS45C::BankFundraiser namespace.

Since you’re familiar with linked lists, and they’re great practice for pointers, we’ll keep using them! You should use the following template structure to store the data:

template <typename T>
struct RankedNode {
  std::string name;
  T value;
  struct RankedNode *next;
};

If you want yo add more things to this struct (e.g., a pointer to previous node), feel free to do so. The struct must have these pre-defined fields though, so you can add things to it, but cannot remove anything.

Similar to project 3, your program should not have a main function. Instead, you will provide your solution as a library!

In the following sections, we will describe the methods your class needs to provide, and further down in submission, you can download a sample header file with this struct and the namespaces defined.

Constructors

Your class should provide two constructors:

FundraiserRank(std::string name);
FundraiserRank(std::string name, bool ascending);

Both constructors take a string as their first parameter, which indicates the name of the competition this rank applies to. The 2nd constructor receives a 2nd parameter, a bool that indicates if the rank is ascending when the parameter is true, or descending when the parameter is false.

An ascending rank means a lower score is better than a higher one. Similarly, a descending rank means a higher score is better than a lower one.

For the 1st constructor, the rank will be ascending by default.

You can assume there will be no ties in the project.

isAscending

bool isAscending();

Returns true if this rank is ascending (i.e., lower is better), false otherwise.

add

unsigned int add(std::string name, T value);

Creates a new RankedNode with the given name/value and adds it to the list in an ordered manner. It should return the position that the node was inserted in.

For example:

  • if you already have the list with values 1.5 - > 2.0 -> 3.1 (ascending==true), and you want to add a node with value 2.3, your final list should be 1.5 - > 2.0 -> 2.3 -> 3.1 and you should return 2, since 2.3 is in “index” 2.

  • if you already have the list with values 15 - > 9.8 -> 3.1 (ascending==false), and you want to add a node with value 20, your final list should be 20 -> 15 - > 9.8 -> 3.1 and you should return 0, since 20 is in “index” 0.

Again, you can assume there are no ties.

isEmpty

bool isEmpty();

Returns true if this rank is currently empty (i.e., no nodes have been added) and false if there are nodes in it.

exists

bool exists(T value);

Returns true if a node matches the given value, false otherwise.

length

unsigned int length();

Returns the current length (number of nodes) in the rank.

find

RankedNode<T>* find(std::string name);

Find and return the node with a matching name. If none exists, return a nullptr.

get

RankedNode<T>* get(int pos);

Find and return the node in the given position.

  • if pos >= 0, you should start at the first node in increasing order (similar to array indices):

    • if you have the list with values 1 -> 2 -> 3, pos==1, would return the node with value 2
    • if you have the list with values 1 -> 2 -> 3, pos==2, would return the node with value 3
  • if pos < 0, you should start from the last node and go back (imagine if you reversed this list and then considered that as array indices):

    • if you have the list with values 1 -> 2 -> 3, pos==-1, would return the node with value 3
    • if you have the list with values 1 -> 2 -> 3, pos==-3, would return the node with value 1
  • if pos is out of bounds, return a nullptr:

    • if you have the list with values 1 -> 2 -> 3, pos==3, would return a nullptr
    • if you have the list with values 1 -> 2 -> 3, pos==4, would return a nullptr
    • if you have the list with values 1 -> 2 -> 3, pos==-4, would return a nullptr

remove

bool remove(std::string name);

If there is a node with a matching name, remove and delete it. If a node was removed, return true; return false if no matching node was found.

reverse

void reverse();

This method should reverse the rank in this list. For example, if this was an ascending rank with a list of values 1 -> 2 -> 3, after calling this method, it should be a descending rank with a list of values 3 -> 2 -> 1.

HINT: Besides modifying the current list, make sure you modify your rank “type” too! If it was an ascending rank, after calling .reverse, it should now be a descending rank. Similarly, if it was a descending rank, after .reverse it should be ascending. In other words, after calling .reverse, the behavior of .isAscending and .add should change to work with the new rank order.

printTop

std::string printTop(unsigned int N);

Create and return a string with the best N nodes on this rank. The format for each node should be name(value).

For example:

  • if you have the list Alice(1)->Bryan(2)->Carlos(3)->Danielle(4) and N == 2, this method should return "Alice(1) -> Bryan(2) -> \n"
  • if you have the list Alice(1)->Bryan(2)->Carlos(3)->Danielle(4) and N == 0, this method should return "\n"
  • if you have the list Christine(3)->Manuel(2)->Sina(1) and N == 4, this method should return "Christine(3) -> Manuel(2) -> Sina(1) -> \n"
  • if you have the list Christine(3)->Manuel(2)->Sina(1) and N == 1, this method should return "Christine(3) -> \n"

If the values are floating-point numbers (float or double), you should use 2 decimal places like in the example above. Here’s an example of how you can do that: https://stackoverflow.com/a/5907076
Hint: you don’t need to test if that’s the type, you can set the precision unconditionally.

Operator overloading

Additionally, you should also overload some operators for this class. If you’re not sure what this means, please take a look at the notes about classes.

operator[]

It should behave similarly to get(pos). For example:

  • if my_rank has the list with values 1 -> 2 -> 3, my_rank[0] should return the node with value 1

operator+(T value)

Should increase the value of all nodes in the list by the given parameter. For example:

  • if my_rank has the list with values 1 -> 2 -> 3, after calling my_rank + 5, the list should have the values 6 -> 7 -> 8.

ostream operator«

This operator allows the user to use your class with cout directly. Check the notes about classes for an example and a reference for a more in-depth explanation.

Should print out the ranking in the following format:

Results for ‘[NAME]’
--------------------
1: node.name (node.value)
2: node.name (node.value)
N: node.name (node.value)
--------------------

If the list is empty, you would print:

Results for ‘[NAME]’
--------------------
--------------------

For example, if myRank has the name of "rubiks cube", and has the list Alice(1.3)->Bryan(2.903)->Carlos(7)->Danielle(40.21), when the user executes cout << my_rank, you should output:

Results for 'rubiks cube'
--------------------
1: Alice (1.30)
2: Bryan (2.90)
3: Carlos (7.00)
4: Danielle (40.21)
--------------------

If the values are floating-point numbers (float or double), you should use 2 decimal places like in the example above. Here’s an example of how you can do that: https://stackoverflow.com/a/5907076
Hints:

  • you don’t need to check the type, you can set the precision unconditionally;
  • it might be helpful to implement this function directly on the class definition instead of having a prototype and a definition later.

Project Hints and Tips

Implementation order

Since we’re giving you total flexibility for implementing the class, it’s hard to partially test things… For example, if we assume you implemented add in a certain way, but you actually did it differently, our tests will never work with your code.

Because of that, for this project, we rely on you to implement a few things so we can check other methods. In particular, you should implement your constructors and the add methods first. After those, we can use your own implementation to set up the tests for the other methods, and it should work with the internals of your class. So after you complete both the constructors and the add method, you should have more choice on what to work next.

Memory leaks

We’re checking for memory leaks in this project! Since you should be using dynamic memory (new), you should make sure you free them when you’re done (delete). When using a class, this is usually done with a destructor, so you can release all memory whenever your object is being deleted.

Submission

Your solution should be named bankFundraiser.h, and it should define and implement the template class described above with all its required methods. You can get a sample bankFundraiser.h here, which defines the appropriate namespaces and has the RankedNode definition that you should use.

You should submit your code on gradescope: https://www.gradescope.com/courses/443728/assignments/2446385 You can submit it as often as you want; just remember that gradescope might not give you instant feedback, and it can take a little bit to compile and run your code.

Your code will be checked for compilation (e.g., warnings), style, static problems (e.g., memory leaks), and functionality issues. Compilation, style, and static checks will be completely open, so you know what needs to be changed. Functionality tests will be a mix of open and hidden ones. You should be able to debug your code based on the open ones.

For this project, all functionality tests use unit tests. That means we’re checking if your functions manipulate the structures the way we want and that your returns are valid. You should not have a main function in your submission.


  1. To get a bonus, clients need to open a new checking account and deposit at least $10,000 into the account within 20 days. The balance in the checking account on the 20th day will determine bonus eligibility and must be maintained for an additional 60 consecutive days. Funds must be new to the bank. The account must be open and in good standing to receive the bonus. ↩︎