In this assignment we're going to build a to-do list application using C++ classes. Don't go on to the next step until you've got the current step working correctly.
In a3.cpp, write a program that prints "Welcome to assignment 3!".
It should also #include the following:
#include "cmpt_error.h"
#include <iostream>
#include <cassert>
cmpt_error.h contains the function cmpt::error(msg)
which,
when called, crashes your programs and prints msg
on the screen.
Other #includes will be used later in the assignment. Only use the #includes specified in the steps! Don't include any other files.
All the code you write for this assignment will be in a3.cpp.
Note Throughout this assignment, you do not need to check for invalid data. You can assume that your functions and methods will always be passed valid data. This is a simplification that isn't acceptable in real life, but we will allow it for his assignment since we want to focus on objects and classes instead of error-checking.
In a3.cpp, write a class called Date
that inherits from the Date_base
class in Date_base.h:
// ...
#include "Date_base.h"
class Date : public Date_base {
private:
// ...
public:
// ...
};
In the private part of Date
, add variables for representing a day (1 to 31),
month (1 to 12), and year (0 or higher). Then add all the getters and setters
listed in Date_base
(they're all public
).
In the public part of Date
, add a constructor that takes a day, month, and
year as input, and uses an initializer list to initialize the private
variables with the passed-in values.
Test what you've done by adding this function to a3.cpp:
void step_2_1_test()
{
Date xmas(25, 12, 2018);
cout << xmas.get_day() << " "
<< xmas.get_month() << " "
<< xmas.get_year() << "\n";
assert(xmas.get_day() == 25);
assert(xmas.get_month() == 12);
assert(xmas.get_year() == 2018);
xmas.set_year(2020);
assert(xmas.get_day() == 25);
assert(xmas.get_month() == 12);
assert(xmas.get_year() == 2020);
cout << "All step_2_1 tests passed!\n";
}
Call it in main()
and compile and run your program to check the results:
// ...
int main()
{
cout << "Welcome to assignment 3!\n";
step_2_1_test();
}
Note Don't start this step until you've finished the previous one.
Uncomment the operator=
method in Date_base.h. In it's body,
add code to assign the day, month, and year from the other
object to the
current object. Finally, return the current object using *this
.
Test what you've done by adding this function to a3.cpp:
void step_2_2_test()
{
Date a(25, 12, 2018);
Date b = a;
assert(a.get_day() == 25);
assert(a.get_month() == 12);
assert(a.get_year() == 2018);
assert(a.get_day() == b.get_day());
assert(a.get_month() == b.get_month());
assert(a.get_year() == b.get_year());
b = Date(1, 1, 2019);
a = b;
assert(a.get_day() == 1);
assert(a.get_month() == 1);
assert(a.get_year() == 2019);
assert(a.get_day() == b.get_day());
assert(a.get_month() == b.get_month());
assert(a.get_year() == b.get_year());
cout << "All step_2_2 tests passed!\n";
}
Call it in main()
and compile and run your program to check the results:
// ...
int main()
{
cout << "Welcome to assignment 3!\n";
step_2_1_test();
step_2_2_test();
}
You should still call the previous test function, just in case you made a change that broke some previous code.
Note Don't start this step until you've finished the previous one.
Uncomment the to_string()
method in Date_base.h. The string
returned by to_string
has this exact format: "dd/mm/yyyy". For example, if
your date object represents May 19 2001, then to_string
returns
"19/05/2001"
. The returned string will always be exactly 10 characters long.
The day and month will always be exactly two digits, and the year will always be
exactly four digits.
Test what you've done by adding this function to a3.cpp:
void step_2_3_test()
{
Date a(25, 12, 2018);
Date b(4, 19, 1999);
Date c(1, 1, 0);
assert(a.to_string() == "25/12/2018");
assert(b.to_string() == "04/19/1999");
assert(c.to_string() == "01/01/0000");
cout << "All step_2_3 tests passed!\n";
}
Call it in main()
and compile and run your program to check the results:
int main()
{
cout << "Welcome to assignment 3!\n";
step_2_1_test();
step_2_2_test();
step_2_3_test();
}
Note Don't start this step until you've finished the previous one.
Add another constructor to your Date
that takes a string as input. The string
is a date that has exactly the same format as the output of to_string
. For
example, Date("25/12/2018")
creates a date object with day 25, month 12, and
year 2018.
Test what you've done by adding this function to a3.cpp:
void step_2_4_test()
{
Date a("25/12/2018");
Date b("04/19/1999");
Date c("01/01/0000");
assert(a.get_day() == 25);
assert(a.get_month() == 12);
assert(a.get_year() == 2018);
assert(b.get_day() == 4);
assert(b.get_month() == 19);
assert(b.get_year() == 1999);
assert(c.get_day() == 1);
assert(c.get_month() == 1);
assert(c.get_year() == 0);
cout << "All step_2_4 tests passed!\n";
}
Call it in main()
and compile and run your program to check the results:
int main()
{
cout << "Welcome to assignment 3!\n";
step_2_1_test();
step_2_2_test();
step_2_3_test();
step_2_4_test();
}
Note Don't start this step until you've finished the previous one.
Sorting dates from earliest to latest is a useful feature that we want to
support. The standard C++ sort
function can sort Date
objects if we
implement operator<
for Date
:
bool operator<(const Date& a, const Date& b) {
// ...
}
This function should return true
if the date a
is earlier than the date b
,
and false
otherwise.
Test what you've done by adding this function to a3.cpp:
void step_2_5_test()
{
Date a("01/01/0000");
Date b("01/01/2018");
Date c("02/01/2018");
Date d("01/02/2018");
assert(a < b);
assert(a < c);
assert(a < d);
assert(b < c);
assert(b < d);
assert(c < d);
assert(!(a < a));
assert(!(b < a));
cout << "All step_2_5 tests passed!\n";
}
Call it in main()
and compile and run your program to check the results:
int main()
{
cout << "Welcome to assignment 3!\n";
step_2_1_test();
step_2_2_test();
step_2_3_test();
step_2_4_test();
step_2_5_test();
}
In a3.cpp, underneath the code you wrote for Date
, write a class
called Todo_item
that inherits from the Todo_item_base
class in
Todo_item_base.h:
// a3.cpp
// ...
#include "Todo_item_base.h"
// ... code for Date ...
class Todo_item : public Todo_item_base {
private:
// ...
public:
// ...
};
In the private part of To_do_item
, add the variables for the item's due date
(use the Date
class you just wrote), a string
description (such as "study
for midterm"), and whether or not it has been completed (an item is either
completed, or not completed).
In the public part of To_do_item
, add a constructor that takes a string
description and Date
due date as input. Use those to initialize the variables
in the private part using an initializer list. The item should always be
initialized as not done.
Then add all the getters and setters, as listed in Todo_item_base
.
Test what you've done by adding this function to a3.cpp:
void step_3_1_test()
{
Date xmas(25, 12, 2018);
Todo_item buy_gifts("Buy gifts", xmas);
assert(buy_gifts.get_description() == "Buy gifts");
assert(buy_gifts.get_due_date().get_day() == xmas.get_day());
assert(buy_gifts.get_due_date().get_month() == xmas.get_month());
assert(buy_gifts.get_due_date().get_year() == xmas.get_year());
assert(!buy_gifts.is_done());
buy_gifts.set_done();
assert(buy_gifts.is_done());
buy_gifts.set_not_done();
assert(!buy_gifts.is_done());
Date earlier(20, 12, 2018);
buy_gifts.set_due_date(earlier);
assert(buy_gifts.get_due_date().get_day() == earlier.get_day());
assert(buy_gifts.get_due_date().get_month() == earlier.get_month());
assert(buy_gifts.get_due_date().get_year() == earlier.get_year());
buy_gifts.set_description("Buy gifts for family");
assert(buy_gifts.get_description() == "Buy gifts for family");
cout << "All step_3_1 tests passed!\n";
}
Call it in main()
to check the results. Make sure to also call all previous
tests in main()
.
Uncomment the to_string
method in Todo_item_base.h. In
it's body, add code to that creates and returns a string representation of the
item. It should have this format: "dd/mm/yyyy? description". The first 10
characters are the due date (from Date::to_string
). The ? is either the
character @
(meaning the item is done), or the character !
(meaning the
item is not done). Then there is one space, followed by a description that
continues to the end of the line.
Test what you've done by adding this function to a3.cpp:
void step_3_2_test()
{
Date xmas(25, 12, 2018);
Todo_item buy_gifts("Buy gifts", xmas);
assert(buy_gifts.to_string() == "25/12/2018! Buy gifts");
buy_gifts.set_done();
assert(buy_gifts.to_string() == "25/12/2018@ Buy gifts");
cout << "All step_3_2 tests passed!\n";
}
Call it in main()
to check the results. Make sure to also call all previous
tests in main()
.
Uncomment the to_html
method in Todo_item_base.h. In its
body, write code that creates and returns a string that can be used as an item
in an HTML. The returned string has one of these formats:
-
<li>dd/mm/yyyy description</li>
if the item is not done. -
<li><del>dd/mm/yyyy description</del></li>
if the item is done.
In both cases, the string is wrapped by <li>
and </li>
tags. If the item is
done, use <del>
/</del>
tags to draw a line through the text when displayed
in HTML. If the item is not done, then don't use <del>
tags.
Notice that there is no @
or !
character after the date in the output
of to_html
.
Test what you've done by adding this function to a3.cpp:
void step_3_3_test()
{
Date xmas(25, 12, 2018);
Todo_item buy_gifts("Buy gifts", xmas);
assert(buy_gifts.to_html_item() == "<li>25/12/2018 Buy gifts</li>");
buy_gifts.set_done();
assert(buy_gifts.to_html_item() == "<li><del>25/12/2018 Buy gifts</del></li>");
Date easter(21, 4, 2019);
Todo_item weave_basket("Weave basket", easter);
assert(weave_basket.to_html_item() == "<li>21/04/2019 Weave basket</li>");
weave_basket.set_done();
assert(weave_basket.to_html_item() == "<li><del>21/04/2019 Weave basket</del></li>");
cout << "All step_3_3 tests passed!\n";
}
Call it in main()
to check the results. Make sure to also call all previous
tests in main()
.
Implement operator<
for Todo_item
. When comparing two items, only the due
date is used in the comparison (the description and completed status are
ignored). So if a
and b
are Todo_item
s, then a < b
is true if the due
date of a
is before the due date of b
, and false otherwise.
Test what you've done by adding this function to a3.cpp:
void step_3_4_test()
{
Date halloween(31, 10, 2018);
Date xmas(25, 12, 2018);
Date easter(21, 4, 2019);
Todo_item carve_pumpkin("Carve pumpkin", halloween);
Todo_item buy_gifts("Buy gifts", xmas);
Todo_item weave_basket("Weave basket", easter);
assert(carve_pumpkin < buy_gifts);
assert(buy_gifts < weave_basket);
assert(carve_pumpkin < weave_basket);
assert(!(carve_pumpkin < carve_pumpkin));
assert(!(buy_gifts < carve_pumpkin));
assert(!(weave_basket < buy_gifts));
assert(!(weave_basket < carve_pumpkin));
cout << "All step_3_4 tests passed!\n";
}
Call it in main()
to check the results. Make sure to also call all previous
tests in main()
.
Add another constructor to Todo_item
that takes a string as input. The string
is formatted exactly the same as the output of Todo_item::to_string
. For
example, Todo_item("25/12/2018! Buy gifts")
creates a Todo_item
with the due
date 25/12/2018, the description "Buy gifts", and a completion status of not
done.
Test what you've done by adding this function to a3.cpp:
void step_3_5_test()
{
Todo_item a("01/01/0000@ buy a hamster");
assert(a.get_description() == "buy a hamster");
assert(a.get_due_date().get_day() == 1);
assert(a.get_due_date().get_month() == 1);
assert(a.get_due_date().get_year() == 0);
assert(a.is_done());
Todo_item b("01/01/2018! sell hamster");
assert(b.get_description() == "sell hamster");
assert(b.get_due_date().get_day() == 1);
assert(b.get_due_date().get_month() == 1);
assert(b.get_due_date().get_year() == 2018);
assert(!b.is_done());
assert(a < b);
assert(!(b < a));
cout << "All step_3_5 tests passed!\n";
}
In a3.cpp, write a class called Todo_list
that inherits from the
Todo_list_base
class in Todo_list_base.h:
// ...
#include "Todo_list_base.h"
class Todo_list : public Todo_list_base {
private:
// ...
public:
// ...
};
In the private part of Todo_list
, add variables for storing 0 or more
Todo_item
s. If you use a vector
to do this, make sure to add #include <vector>
to the list of includes at the top of the file.
In the public part of Todo_list
, add a default constructor that creates a
Todo_list
with no items (size 0). Also, implement all the getters and setters
listed in Todo_list_base.h
.
Test what you've done by adding this function to a3.cpp:
void step_4_1_test()
{
Date easter(21, 4, 2018);
Date halloween(31, 10, 2018);
Todo_item weave_basket("Weave basket", easter);
Todo_item carve_pumpkin("Carve pumpkin", halloween);
Todo_list list;
assert(list.size() == 0);
list.add_item(weave_basket);
assert(list.size() == 1);
Todo_item item = list.get_item(0);
Date date = item.get_due_date();
assert(item.get_description() == weave_basket.get_description());
assert(date.get_day() == weave_basket.get_due_date().get_day());
assert(date.get_month() == weave_basket.get_due_date().get_month());
assert(date.get_year() == weave_basket.get_due_date().get_year());
list.add_item(carve_pumpkin);
assert(list.size() == 2);
list.remove_item(0);
assert(list.size() == 1);
item = list.get_item(0);
date = item.get_due_date();
assert(item.get_description() == carve_pumpkin.get_description());
assert(date.get_day() == carve_pumpkin.get_due_date().get_day());
assert(date.get_month() == carve_pumpkin.get_due_date().get_month());
assert(date.get_year() == carve_pumpkin.get_due_date().get_year());
list.remove_item(0);
assert(list.size() == 0);
cout << "All step_4_1 tests passed!\n";
}
Uncomment the read_from_file
method in Todo_list_base.h,
and then implement it in a3.cpp. The method reads the contents of
filename
, adding each item to the list. If the file does not exist, it should
do nothing and no change is made to the list.
Each line of filename
is formatted exactly the same as the output of
Todo_item::to_string
. todo_example.txt is an example of
such a file. It contains 11 Todo_item
s, and is used in the tests below.
Add #include <fstream>
to the list of includes at the top of your
a3.cpp.
Test what you've done by adding this function to a3.cpp:
void step_4_2_test()
{
Todo_list list;
list.read_from_file("todo_example.txt");
assert(list.size() == 11);
// 13/02/2023! optometrist in afternoon
Todo_item eyes("optometrist in afternoon", Date(13, 2, 2023));
Todo_item item = list.get_item(0);
Date date = item.get_due_date();
assert(item.get_description() == eyes.get_description());
assert(date.get_day() == eyes.get_due_date().get_day());
assert(date.get_month() == eyes.get_due_date().get_month());
assert(date.get_year() == eyes.get_due_date().get_year());
cout << "All step_4_2 tests passed!\n";
}
Uncomment the write_to_file(filename)
method in
Todo_list_base.h. When called, it should first sort the
items by due date (earliest to latest), and then write each of those due dates
to filename
. If the file does not exist, it should create it. If the file
already exists, then it will get over-written (be careful!).
The output of write_to_file
should be formatted in exactly the same style as
todo_example.txt. That way, you can read the file back in
using read_from_file
.
Similarly, uncomment write_to_html_file(filename)
in
Todo_list_base.h. When called, it first sorts the items by
due date (earliest to latest), and then writes each of them to filename
using
their to_html_item
. Also, the first line of the file is the HTML tag <ul>
,
and the last line is </ul>
.
Add #include <algorithm>
to the list of includes at the top of your
a3.cpp so that you can use the standard C++ sort
function.
Test what you've done by adding this function to a3.cpp:
void step_4_3_test()
{
Todo_list list;
list.add_item(Todo_item("Buy tinsel", Date(20, 12, 2018)));
list.add_item(Todo_item("21/04/2018@ Weave basket"));
list.add_item(Todo_item("Carve pumpkin", Date(31, 10, 2018)));
list.write_to_file("step_4_3_output.txt");
list.write_to_html_file("step_4_3_output.html");
// read step_4_3_output.txt back in and compare to list
Todo_list list2;
list2.read_from_file("step_4_3_output.txt");
assert(list2.size() == 3);
assert(list2.get_item(0).get_description() == "Weave basket");
assert(list2.get_item(1).get_description() == "Carve pumpkin");
assert(list2.get_item(2).get_description() == "Buy tinsel");
assert(list2.get_item(0).get_due_date().get_day() == 21);
assert(list2.get_item(0).get_due_date().get_month() == 4);
assert(list2.get_item(0).get_due_date().get_year() == 2018);
assert(list2.get_item(1).get_due_date().get_day() == 31);
assert(list2.get_item(1).get_due_date().get_month() == 10);
assert(list2.get_item(1).get_due_date().get_year() == 2018);
assert(list2.get_item(2).get_due_date().get_day() == 20);
assert(list2.get_item(2).get_due_date().get_month() == 12);
assert(list2.get_item(2).get_due_date().get_year() == 2018);
assert(list2.get_item(0).is_done() == true);
assert(list2.get_item(1).is_done() == false);
assert(list2.get_item(2).is_done() == false);
cout << "All step_4_3 tests run: check the HTML results by hand!\n";
}
Here are the output files created by the test: step_4_3.txt, step_4_3.html. Make sure to manually check that step_4_3.html looks correct.
As a final test of your a3.cpp, write and test a void
function
called part5()
that reads the file part5_todos.txt and then
prints this:
done: 476
not done: 524
oldest: 05/01/2018@ freeze-dry magnet-shaped green orange
newest: 28/12/2023@ defrost large yellow cherry
Print the output exactly in this format.
In addition, make a file called final_output.txt
created by a call to
write_to_file
. And make a file called final_output.html
created by a call to
write_to_html_file
.
When your part5()
function is tested, the contents of part5_todos.txt
will
behave the same format as todo_example.txt, but the content
of the todos, and number of todos, may be different.
Please put all your code into a3.cpp, and submit it on Canvas. Implement all the methods and functions exactly as described, otherwise the marking software will probably give you 0!
The only file you submit is a3.cpp: don't submit any other files. The marker will use the standard makefile to compile it, and copies of cmpt_error.h and Date_base.h, Todo_item_base.h, and Todo_list_base.h will be in the same folder as a3.cpp when it's compiled and tested.
Before we give your program any marks, it must meet the following basic requirements:
-
It must compile on Ubuntu Linux using the standard course makefile:
$ make a3 g++ -std=c++17 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g a3.cpp -o a3
If your program fails to compile, your mark for this assignment will be 0.
A copy of cmpt_error.h will be in the same folder as a3.cpp when it's compiled, so your program can use
cmpt::error
if necessary. -
It must have no memory leaks or memory errors, according to
valgrind
, e.g.:$ valgrind ./a3 // ... lots of output ...
A program is considered to have no memory error if:
-
In the
LEAK SUMMARY
,definitely lost
,indirectly lost
, andpossibly lost
must all be 0. -
The
ERROR SUMMARY
reports 0 errors. -
It is usually okay if still reachable reports a non-zero number of bytes.
-
-
You must include the large comment section with student info and the statement of originality. If your submitted file does not have this, then we will assume your work is not original and it will not be marked.
If your program meets all these basic requirements, then it will graded using the marking scheme on Canvas.
- All code is sensibly and consistently indented, and all lines are 100 characters in length, or less.
- Whitespace is used to group related pieces of a code to make it easier for humans to read. All whitespace should have a purpose.
- Variable and function names are self-descriptive.
- Appropriate features of C++ are used, as discussed in class and in the notes. Note If you use a feature that we haven't discussed in class, you must explain it in a comment, even if you think it's obvious.
- Comments are used when needed to explain chunks of code whose purpose is not obvious from the code itself. There should be no commented-out code from previous versions.
- No unnecessary work is done.
- No unnecessary memory is used.
To get full marks, your functions must pass all the test cases the marker uses for that question. The marker may use test cases not given in the assignment.
- 1 mark for correct code for each of 2.1, 2.2, 2.3, 2.4, and 2.5
- 1 mark for correct code for each of 3.1, 3.2, 3.3, 3.4, and 3.5
- 1 mark for correct code for each of 4.1, 4.2, 4.3
- 5 marks for part 5, for printing the correct number todos that are done
and not done, and the oldest and newest todos. Also, both
final_output.txt
andfinal_output.html
are correctly printed.
- -1 mark (at least) if your file does not have the correct name, or you submit it in the incorrect format, submit the wrong file, etc.
- up to -3 marks if you do not include your full name, email, and SFU ID in the header of your file.
- a score of 0 if you don't include the "statement of originality in the header of your file.
- a score of 0 if you accidentally submit a "wrong" non-working file, and then after the due date submit the "right" file. If you can provide evidence that you finished the assignment on time, then it may be marked (and probably with a late penalty).