Homework 1 gave you extensive experience with the Sequence type using both arrays and dynamically-allocated arrays. In this project, you will re-write the implementation of the Sequence type to employ a doubly-linked list rather than an array. You must not use arrays. You will also implement a couple of algorithms that operate on sequences.
Implement Sequence yet again
Consider the Sequence interface from problem 2 of Homework 1:
using ItemType = TheTypeOfElementGoesHere;
class Sequence
{
public:
Sequence();
bool empty() const;
int size() const;
int insert(int pos, const ItemType& value);
int insert(const ItemType& value);
bool erase(int pos);
int remove(const ItemType& value);
bool get(int pos, ItemType& value) const;
bool set(int pos, const ItemType& value);
int find(const ItemType& value) const;
void swap(Sequence& other);
};
In problem 3 of Homework 1, you implemented this interface using an array. For this project, implement this Sequence interface using a doubly-linked list. (You must not use the list class template from the C++ library.)
For the array implementation of problem 3 of Homework 1, since you declared no destructor, copy constructor, or assignment operator, the compiler wrote them for you, and they did the right thing. For this linked list implementation, if you let the compiler write the destructor, copy constructor, and assignment operator, they will do the wrong thing, so you will have to declare and implement these public member functions as well:
Destructor: When a Sequence is destroyed, the nodes in the linked list must be deallocated.
Copy constructor: When a brand new Sequence is created as a copy of an existing Sequence, enough new nodes must be allocated to hold a duplicate of the original list.
Assignment operator: When an existing Sequence (the left-hand side) is assigned the value of another Sequence (the right-hand side), the result must be that the left-hand side object is a duplicate of the right-hand side object, with no memory leak of list nodes (i.e. no list node from the old value of the left-hand side should be still allocated yet inaccessible).
Notice that there is now no a priori limit on the maximum number of items in the Sequence (so the one-argument form of insert should never return 1). Notice also that, as in Homework 1, if a Sequence has a size of n, then the values of the first parameter to get for which that function retrieves an item (that was previously inserted by a call to one of the insert member functions) and returns true are 0, 1, 2, ..., n1; for other values, it returns false without setting its second parameter. For example:
Sequence ss; // ItemType is std::string
ss.insert(0, "aaa");
ss.insert(1, "bbb");
ss.insert(2, "ccc");
ItemType x = "xxx";
assert(!ss.get(3, x) && x == "xxx"); // x is unchanged
assert(ss.get(1, x) && x == "bbb");
This is the same visible behavior as in Homework 1.
Another requirement is that as in Problem 5 of Homework 1, the number of statement executions when swapping two sequences must be the same no matter how many items are in the sequences.
Implement some sequence algorithms
Implement the following two functions. Notice that they are non-member functions: They are not members of Sequence or any other class, so they must not access private members of Sequence.
int subsequence(const Sequence& seq1, const Sequence& seq2);
Consider all the items in seq2; let's call them seq20, seq21, ..., seq2n. If there exists at least one k such that seq1k == seq20 and seq1k+1 == seq21 and ... and seq1k+n == seq2n, and k+n < seq1.size(), then this function returns the smallest such k. (In other words, if seq2 is a consecutive subsequence of seq1, the function returns the earliest place in seq1 where that subsequence starts.) If no such k exists or if seq2 is empty, the function returns -1. For example, if seq1 were a sequence of ints consisting of
30 21 63 42 17 63 17 29 8 32
and seq2 consists of
63 17 29
then the function returns 5, since the consecutive items 63 17 29 appear in seq1 starting with the 63 at position 5. (The result is not 2, because while there's a 63 at position 2, followed eventually by a 17 and a 29, those items are not consecutive in seq1.) With the same seq1, if seq2 consists of
17 63 29
void interleave(const Sequence& seq1, const Sequence& seq2, Sequence& result);
This function produces as a result a sequence that consists of the first item in seq1, then the first in seq2, then the second in seq1, then the second in seq2, etc.
More formally: Consider the items in seq1; let's call them seq10, seq11, ..., seq1m, and let's call the items seq2 contains seq20, seq21, ..., seq2n. If m equals n, then when this function returns, result must consist of the sequence seq10, seq11, seq21, ..., seq1m, seq2n, and no other items.
If m is less than n, then when this function returns, result must consist of the sequence seq10, seq20, seq11, seq21, ..., seq1m, seq2n, seq1m+1, seq1m+2, ..., seq1n, and no other items.
If n is less than m, then when this function returns, result must consist of the sequence seq10, seq20, seq11, seq21, ..., seq1n, seq2n, seq1n+1, seq1n+2, ..., seq1m, and no other items.
If seq1 is empty, then when this function returns, result must consist of the sequence seq20, ..., seq2n, and no other items. If seq2 is empty, then when this function returns, result must consist of the sequence seq10, ..., seq1m, and no other items. If seq1 and seq2 are empty, then when this function returns, result must be empty.
When this function returns, result must not contain any items that this spec does not require it to contain. (You must not assume result is empty when it is passed in to this function; it might not be.)
As an example, if seq1 were a sequence of ints consisting of
30 21 63 42 17 63
and seq2 consists of
42 63 84 19
then no matter what value it had before, result ends up consisting of:
30 42 21 63 63 84 42 19 17 63
Be sure that in the face of aliasing, these functions behave as this spec requires: Does your implementation work correctly if seq1 and result refer to the same Sequence, for example?
Regardless of how much work you put into the assignment, your program will receive a zero for correctness if you violate these requirements:
bool Sequence::erase(int pos)
{
return true; // not correct, but at least this compiles
}
void interleave(const Sequence& seq1, const Sequence& seq2, Sequence& result)
{
// does nothing; not correct, but at least this compiles
}
#include "Sequence.h"
#include < type_traits>
#define CHECKTYPE(f, t) { auto p = static_cast< t>(f); (void)p; }
static_assert(std::is_default_constructible< Sequence>::value,
"Sequence must be default-constructible.");
static_assert(std::is_copy_constructible< Sequence>::value,
"Sequence must be copy-constructible.");
static_assert(std::is_copy_assignable< Sequence>::value,
"Sequence must be assignable.");
void thisFunctionWillNeverBeCalled()
{
CHECKTYPE(&Sequence::empty, bool (Sequence::*)() const);
CHECKTYPE(&Sequence::size, int (Sequence::*)() const);
CHECKTYPE(&Sequence::insert, int (Sequence::*)(int, const ItemType&));
CHECKTYPE(&Sequence::insert, int (Sequence::*)(const ItemType&));
CHECKTYPE(&Sequence::erase, bool (Sequence::*)(int));
CHECKTYPE(&Sequence::remove, int (Sequence::*)(const ItemType&));
CHECKTYPE(&Sequence::get, bool (Sequence::*)(int, ItemType&) const);
CHECKTYPE(&Sequence::set, bool (Sequence::*)(int, const ItemType&));
CHECKTYPE(&Sequence::find, int (Sequence::*)(const ItemType&) const);
CHECKTYPE(&Sequence::swap, void (Sequence::*)(Sequence&));
CHECKTYPE(subsequence, int (*)(const Sequence&, const Sequence&));
CHECKTYPE(interleave, void (*)(const Sequence&, const Sequence&, Sequence&));
}
int main()
{}
#include "Sequence.h"
#include < string>
#include < iostream>
#include < cassert>
using namespace std;
void test()
{
Sequence s;
assert(s.insert(0, "lavash") == 0);
assert(s.insert(0, "tortilla") == 0);
assert(s.size() == 2);
ItemType x = "injera";
assert(s.get(0, x) && x == "tortilla");
assert(s.get(1, x) && x == "lavash");
}
int main()
{
test();
cout << "Passed all tests" << endl;
}
#include "Sequence.h"
#include < iostream>
#include < cassert>
using namespace std;
void test()
{
Sequence s;
assert(s.insert(0, 10) == 0);
assert(s.insert(0, 20) == 0);
assert(s.size() == 2);
ItemType x = 999;
assert(s.get(0, x) && x == 20);
assert(s.get(1, x) && x == 10);
}
int main()
{
test();
cout << "Passed all tests" << endl;
}