The undoing is always more difficult than the doing. -- Kate DiCamillo
In this assignment, your task is to add an undo feature to a list of strings.
To start, here's a working class called Stringlist that implements a simple string list as a dynamic array. Stringlist_test.cpp has tests for all the methods in Stringlist.
Stringlist
has one unimplemented method:
//
// Undoes the last operation that modified the list. Returns true if a
// change was undone, false otherwise.
//
bool undo()
{
cout << "Stringlist::undo: not yet implemented\n";
return false;
}
Your job is to implement undo
, thus making Stringlist
an undoable list.
Your implementation must follow these rules:
- Do not delete any methods, or change the signatures of any methods, in
Stringlist. You can change the implementation of
existing methods if necessary. But they should still work the same way: your
finished version of
Stringlist
withundo
implement must still pass all the tests in Stringlist_test.cpp. - You can add other helper methods (public or private), functions, and classes/structs to Stringlist.h if you need them.
- You must implement
undo()
using a private stack that is accessible only inside theStringlist
class. Implement the stack yourself as a linked list. Do not use arrays, vectors, or any other data structure for your stack. - Do not use any other #includes or #pragmas in Stringlist.h other than the ones already there.
When it's done, you'll be able to write code like this:
#include "Stringlist.h"
#include <iostream>
using namespace std;
int main() {
Stringlist lst;
cout << lst << endl; // {}
lst.insert_back("one");
lst.insert_back("two");
lst.insert_back("three");
cout << lst << endl; // {"one", "two", "three"}
lst.undo();
cout << lst << endl; // {"one", "two"}
lst.undo();
cout << lst << endl; // {"one"}
lst.undo();
cout << lst << endl; // {}
}
To start, download all the files for this assignment to your computer, and then compile and run Stringlist_test.cpp to make sure it runs without error:
> make Stringlist_test
g++ -std=c++17 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g Stringlist_test.cpp -o Stringlist_test
> valgrind ./Stringlist_test
... test output ...
Running your program with valgrind
is important, since it will help you find
memory leaks, and other memory-related errors.
Note There should be no errors when you run this testing! If you have any, double-check that you are running exactly the correct file, and using the correct compiler version, correct options, and correct makefile.
As mentioned above, you must implement undo()
using at least one private
stack implemented as a linked list inside the Stringlist
class. You can
modify Stringlist
only as described at the start of this assignment.
One idea for how undo can work is that every time Stringlist
is modified by
one of its methods, it pushes the inverse operation on the top of an undo
stack. When undo()
is called, it pops the top of the stack and applies that
operation to the list, thus undoing the most recent operation.
Important All the methods in Stringlist marked "undoable"
should work with undo()
. undo()
cannot be undone: there is no "re-do"
feature in this assignment.
Here are some examples of how specific methods should work.
Suppose lst
is {"dog", "cat", "tree"}
, and you call lst.insert_before(3, "hat")
, making it {"dog", "cat", "tree", "hat"}
. It should push the operation
remove string at index 3 on the undo stack. You could store it as a short
string
command, e.g. REMOVE 3
. If you now call lst.undo()
, the REMOVE 3
command on top of the stack is popped and applied to the list, e.g. the string
at index 3 is removed, leaving the list in the state it was in before calling
insert_before
: {"dog", "cat", "tree"}
.
In code:
// lst == {"dog", "cat", "tree"}
lst.insert_before(3, "hat");
// lst == {"dog", "cat", "tree", "hat"}
lst.undo();
// lst == {"dog", "cat", "tree"}
lst.insert_before(1, "shoe");
// lst == {"dog", "shoe", "cat", "tree"}
lst.undo();
// lst == {"dog", "cat", "tree"}
For set
, suppose that lst
is {"yellow", "green", "red", "orange"}
, and so
lst.get(2)
returns "red"
. If you call lst.set(2, "cow")
, then you should
push the operation set location 2 to "red"
onto the undo stack, and then
over-write location 2 with "cow"
. You could format the operation like "SET 2 red"
. Calling lst.undo()
pops the top command of the stack and applies it to
the list, e.g. the string at index 2 is set to "red"
and the list is in the
state it was in before calling set
: {"yellow", "green", "red", "orange"}
.
In code:
// lst == {"yellow", "green", "red", "orange"}
lst.set(2, "cow");
// lst == {"yellow", "green", "cow", "orange"}
lst.undo();
// lst == {"yellow", "green", "red", "orange"}
For remove_at
, suppose lst
is {"dog", "cat", "tree"}
. If you call
lst.remove_at(1)
, you should then push the operation insert "cat"
at index
1 onto the stack, and then remove the string at index 1 so it becomes {"dog", "cat", "tree"}
. You could format the operation as "INSERT 1 cat"
. If you now
call lst.undo()
, the command on top of the stack is popped and applied to the
list, e.g. the string "cat"
is inserted at index 1, and the list is in the
state it was in before calling remove_at
: {"dog", "cat", "tree"}
.
In code:
// lst == {"dog", "cat", "tree"}
lst.remove_at(1);
// lst == {"dog", "tree"}
lst.undo();
// lst == {"dog", "cat", "tree"}
For operator=
, suppose lst1
is {"dog", "cat", "tree"}
, and lst2
is
{"yellow", "green", "red", "orange"}
. If you call lst1 = lst2;
, then you
should push the command set lst1
to {"dog", "cat", "tree"}
onto the stack,
and then assign lst1
to lst2
. You could format the command as "SET lst1 dog cat tree"
. Calling lst1.undo()
pops the command on top of the stack and
applies it to the list, e.g. lst1
is set to the state it was in before calling
operator=
: {"dog", "cat", "tree"}
.
In code:
// lst1 == {"dog", "cat", "tree"}
// lst2 == {"yellow", "green", "red", "orange"}
lst1 = lst2;
// lst1 == {"yellow", "green", "red", "orange"}
// lst2 == {"yellow", "green", "red", "orange"}
lst1.undo();
// lst1 == {"dog", "cat", "tree"}
// lst2 == {"yellow", "green", "red", "orange"}
As this shows, when you undo operator=
, the entire list of strings is
restored in one call to undo()
.
Important notes:
-
If
lst1
andlst2
are different objects, then whenlst2
is assigned tolst1
just the underlying string array oflst2
is copied tolst1
. Thelst1
undo stack is updated so that it can undo the assignment. The undo stack oflst2
is not copied, andlst2
is not modified in any away. -
Self-assignment is when you assign a list to itself, e.g.
lst1 = lst1;
. In this case, nothing happens tolst1
. Both its string data and undo stack are left as-is.
For remove_all
, suppose lst
is {"dog", "cat", "tree"}
. If you call
lst.remove_all()
, then you should push the operation set lst1
to {"dog", "cat", "tree"}
onto the stack, and then remove all the strings from lst
(i.e. its size is 0). You could format the command as "SET lst1 dog cat tree"
.
Calling lst1.undo()
pops the command on top of the stack and applies it to the
list, e.g. lst
is set to {"dog", "cat", "tree"}
and the list is in the state
it was in before calling remove_all
: {"dog", "cat", "tree"}
.
In code:
// lst == {"dog", "cat", "tree"}
lst.remove_all();
// lst == {}
lst.undo();
// lst == {"dog", "cat", "tree"}
Note that it should work the same way when lst
is empty:
// lst == {}
lst.remove_all();
// lst == {}
lst.undo();
// lst == {}
undo()
should undo all the other methods in Stringlist that
are marked as "undoable" in the source code comments.
As mentioned above, undo()
is not undoable. There is no "re-do" feature in
this assignment.
It's important to test your code as you go. In a2_test.cpp, write
code to test your Stringlist
undo method. Compile and run it like this:
❯ make a2_test
g++ -std=c++17 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g a2_test.cpp -o a2_test
> ./a2_test
... testing output ...
You should test at least the examples given above, plus every other function
that can be undone. Follow the testing style in
Stringlist_test.cpp and use assert
s or if
-statements
to test your code.
When you're done, submit just your Stringlist.h on Canvas. Don't submit anything else.
The marker will compile and run your program on Ubuntu Linux using makefile and a command like this:
> make a2_test
g++ -std=c++17 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g a2_test.cpp -o a2_test
> ./a2_test
... testing output ...
> valgrind ./a2_test
... valgrind output ...
If the marker can't compile your program, then you will get 0 marks for the assignment.
The markers a2_test.cpp
will contain their tests (that you have probably not
seen before) to check the correctness of you Stringlist
. a2_test.cpp
will
#include your Stringlist.h, and it should run without needing
any other #includes in a2_test.cpp
(all the #includes your Stringlist
need
should be in your Stringlist.h.
The marker will also run your Stringlist.h with
Stringlist_test.cpp to ensure that all the original
Stringlist
functionality still works:
> make Stringlist_test
g++ -std=c++17 -Wall -Wextra -Werror -Wfatal-errors -Wno-sign-compare -Wnon-virtual-dtor -g Stringlist_test.cpp -o Stringlist_test
> ./Stringlist_test
... testing output ...
Note The compilation commands are quite strict! Make sure your program compiles with them before submitting it.
- 8 marks One mark for each method in Stringlist.h marked
"undoable" that works correctly with
undo()
. This also includes the correct behaviour for theStringlist
copy constructor (which should not copy the undo stack). - 5 marks The markers tests run correctly, including with no memory leaks
according to
valgrind
. - 5 marks
Stringlist
passes all tests in Stringlist_test.cpp.
- 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 has 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 code whose purpose is not obvious from the code itself. There should be no commented-out code from previous versions.
- -5 marks if your program does not compile with the given
makefile and the
make
commands given in the assignment. If the marker can't get your program to compile at all, then you will get a score of 0 for the assignment. - -5 marks if you change the name of the file or class, or if you change the signatures of any give methods or functions. Each individual difference is -5 marks.
- -10 marks if you use arrays, vectors, or other containers to implement the
private stack in
Stringlist
. You must use a linked list to implement the stack. - 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", or it is modified in any way.
- a score of 0 if you 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.