1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
#include <string>
#include <cassert>
#include <iostream>
#include <iomanip>
struct str_list
{
// default constructor that creates an empty linked list
// the explicitly defaulted default constructor would creates an empty list
// note: head and tail have default member initialiser that initialise them to null
str_list() = default ;
// destructor that releases all the nodes.
~str_list()
{
assert( is_valid() ) ; // sanity check: the list is in a valid state
while( !empty() ) pop_back() ;
};
// disable copy and move for now (you may ignore this nicety for now)
str_list( const str_list& ) = delete ;
str_list( str_list&& ) = delete ;
str_list& operator= ( str_list ) = delete ;
bool empty() const
{
assert( is_valid() ) ; // sanity check: the list is in a valid state
return head == nullptr ;
}
// member functions for inserting and erasing at the front and back
void push_front( std::string str )
{
assert( is_valid() ) ; // sanity check: the list is originally in a valid state
head = new node { str, head } ;
if( tail == nullptr ) tail = head ; // edgecase: insert into empty list
assert( is_valid() ) ; // sanity check: the modified list is in a valid state
}
void push_back( std::string str )
{
assert( is_valid() ) ; // sanity check: the list is originally in a valid state
if( empty() ) push_front(str) ; // insert into empty list
else
{
tail->next = new node {str} ; // add a node after the current tail
tail = tail->next ; // and make it the new tail
}
assert( is_valid() ) ; // sanity check: the modified list is in a valid state
}
void pop_front()
{
assert( is_valid() ) ; // sanity check: the list is in a valid state
if( !empty() )
{
auto temp = head ;
head = head->next ;
delete temp ;
if( head == nullptr ) tail = nullptr ; // edge case: erased the only element
}
assert( is_valid() ) ; // sanity check: the modified list is in a valid state
}
void pop_back()
{
assert( is_valid() ) ; // sanity check: the list is in a valid state
if( head == tail ) pop_front() ; // erase the only element or empty list
else
{
// get to the node just before the tail
node* just_before_tail = head ;
while( just_before_tail->next != tail )
just_before_tail = just_before_tail->next ;
delete tail ; // delete the old tail
tail = just_before_tail ; // and make this the new tail
tail->next = nullptr ;
}
assert( is_valid() ) ; // sanity check: the modified list is in a valid state
}
// display function that displays the current contents
std::ostream& display( std::ostream& stm = std::cout ) const
{
assert( is_valid() ) ; // sanity check: the list is in a valid state
stm << "[ " ;
for( node* n = head ; n != nullptr ; n = n->next )
std::cout << std::quoted(n->value) << ' ' ;
return stm << ']' ;
}
private:
struct node
{
explicit node( std::string v, node* n = nullptr ) : value(v), next(n) {}
std::string value ;
node* next = nullptr ; // default member initialiser
};
node* head = nullptr ; // default member initialiser
node* tail = nullptr ; // default member initialiser
// class invariant: (if our code is correct, this must always be true)
// this should be the first function that we write when we implement a class
bool is_valid() const
{
// sanity check: either both head and tail are non-null
// or both head and tail are null
if( ( head && !tail ) || ( !head && tail ) ) return false ;
// there must be nothing after the tail; it must be the last node
if( tail && tail->next != nullptr ) return false ;
// if we start from head, we must be able to reach the tail
for( node* n = head ; n != tail ; n = n->next )
if( n == nullptr ) return false ; // we won't be able to reach the tail
return true ; // things seem to be fine
}
// provide an overloaded stream insertion operator (you may ignore this nicety for now)
friend std::ostream& operator<< ( std::ostream& stm, const str_list& lst )
{ return lst.display(stm) ; }
};
int main()
{
str_list lst ; // create a StringList object.
lst.display() << '\n' ;
const std::string test_strings[] = { "1.One,", "2.two,", "3.Buckle", "4.my", "5.shoe;",
"6.Three,", "7.four", "8.Knock", "9.at", "10.the", "11.door." } ;
// start inserting new nodes, one at a time.
bool at_front = true ;
for( std::string str : test_strings )
{
// Alternate between inserting at the front and back.
if(at_front) lst.push_front(str) ; else lst.push_back(str) ;
at_front = !at_front ; // toggle
// call display member function to display the updated list.
lst.display() << '\n' ;
}
std::cout << '\n' ;
// start erasing nodes, one at a time.
at_front = true ;
for( std::string str : test_strings )
{
// Alternate between erasing at the front and back.
if(at_front) lst.pop_front() ; else lst.pop_back() ;
at_front = !at_front ; // toggle
// call display member function to display the updated list.
// for variety, use the overloaded operator
std::cout << lst << '\n' ;
}
}
|