From COS132 to COS110 : Programming Exercise for the Winter Recess Weeks
________________________________________________________________________________
(Education Letter, by Prof.G., July 2025)
________________________________________________________________________________
Remember from COS151 : The Abstract Algebraic Data Type LIST(D) with the operations
•
•
•
•
•
•
Task:
•
•
create:
is_empty:
head:
tail:
insert:
append:
→ L(D)
L(D)
→ bool
L(D)
→D
L(D)
→ L(D)
D × L(D) → L(D)
L(D) × L(D) → L(D)
Assume that the Data Domain D is the sort of the integer numbers.
Under this assumption, provide a C++ implementation of the given data type!
How can we represent D in C++ when we assume (as above) that D are the integer numbers ?
typedef int D ;
The implementation needs more "flesh" than the "skeleton" of the abstract specification of above.
The specification only tells us what must be implemented ‒ however not: how.
In our C++ implementation we wish to use structs and pointers.
Thereby we are introducing new information that was not already given in the abstract specification.
In the following we recursively define an list element, L, as a pair of a D item together with a link
to another list element :
typedef struct element { D dat ; element* nxt ; } L ;
On this basis it will be fairly easy and straightforward to implement all of the Algebraic Data Type's
above-mentioned operations. As the given Data Type has six operations, we will use six functions
in C++ for their implementations.
As C++ (in contrast to JAVA) makes it difficult for us to implement functions that "take" and return
entire data structures (as in the abstract specification of above), we slightly deviate from the abstract
specification by letting our functions "work" with pointers to the data structures (instead of the data
structures themselves as such):
L* create()
{
}
static L* nothing ; // Comment : Read the literature about static !
nothing = nullptr ;
return( nothing ) ; // Convince yourself about why this makes good sense !
The following implementation of the Boolean function is straightforward:
bool isEmpty( L* list )
{
return( list==nullptr ) ;
}
Turn the page →
In order to understand the following C++ implementations of the remaining four functions, you
must remember the ALGEBRAIC LAWS of the Abstract Data Type LIST(D) as they had been
shown to you in Prof.G's course module COS151 this year. You can find these laws in your own
COS151 cribnotes which you had taken during the semester. Now we must make sure that all of the
following implementations are fully compliant with (i.e.: not in contradiction against) the laws.
The two functions head and tail are very easy and straightforward, as they are only "reading from"
a given list data structure without modifying the data structure itself. For the head function we can
choose whether we wish to return a copy of the data element, or whether we rather wish to return a
pointer to the data element, which is "a matter of taste". Both alternatives can "work" equally well.
Very important: According to the above-mentioned Laws these functions are un-defined in the case
that the given list is empty! In other words: Before we can call such a function in the main program,
we must already have invoked the above-mentioned isEmpty test first !
D head( L* list ) // return copy of the head
{
return( (*list).dat ) ;
}
D* head( L* list )// return pointer to the head
{
D* h = &( (*list).dat ) ;
return( h ) ;
}
L* tail( L* list )
{
return( (*list).nxt ) ; // Note: The tail of a non-empty list is a list !
}
According to the Laws of the List Type from course COS151, the implementation of insert is quite
straightforward. Again, however, we can choose (as a "matter of taste") whether we wish to "feed"
the function with a copy of ‒or a pointer to‒ the data element which shall be inserted. Both these
possibilities are shown below, (whereby the pointer option is actually much simpler than the copy
option):
L* insert( D newNat , L* list)
L* insert( L* newHead , L* list )
{ // Implementation = exercise for students!
{
// Hint: make an element "around" newDat,
(*newHead).nxt = list ; // connect !
// connect list to the nxt of the new element,
return( newHead ) ;
// make a new pointer to this new element,
}
// and finally return this pointer.
}
The implementation of the append function is the most difficult ‒but also the most interesting‒ part
of this exercise because the operations of above do not allow us to directly access the ultimate data
element of the 1st list to which the 2nd list shall be appended. Fortunately, the Laws of the List can
provide us with a helpful hint, as one of those list-laws states the recursive algebraic equation
append( L1 , L2 ) = insert( head(L1) , append( tail(L1) , L2 ))
which utilises the other functions ‒insert, head, tail‒ of which an implementation already exists :)
As we have different versions of the head function as well as also different versions of the insert
function as shown above, we now have various slightly different possibilities of implementing the
append function, too. For the sake of brevity only one these possibilities is presented on the next
page, whilst the students are encouraged to "try out" (by themselves) some of the other possibilities.
Turn the page →
L* append( L* firstList, L* secondList )
{
if (firstList==nullptr) { return(secondList) ; } // Algebraic List-Law !
else
{
L* longList = append( tail(firstList), secondList ) ; // Recursion !
(*firstList).nxt = longList ; // Re-connect the head to the long list
}
return( firstList );
}
Finally we would like to test whether everthing works well as intended. For a test we would like to
apply the following ALGORITHM:
• Create a new list: L1
• Check whether it is empty
• Insert some data into it
• Check whether it is still empty
• Insert some more data into it
• Create another new list: L2
• Check whether it is empty
• Insert some data into it
• Check whether it is still empty
• Insert some more data into it
• Append L1,L2 to yield yet another list: L3
• Retrieve ("read out") the 3rd element from L3 ( as in the Aegrotat Exam of COS151 ! )
The following C++ main program implements this test (whereby the "pointer versions" of above are
used for head and insert) :
int main()
{
// we make the first list, and let a pointer point to it:
L* list1 = create() ;
std::cout << ( isEmpty(list1) ) ;
// we make some data element
D one = 1 ;
L a;
a.dat = one ;
a.nxt = nullptr ;
L* elem1 ;
elem1 = &a ;
// we update the list and check whether it is still empty
list1 = insert( elem1 , list1 ) ;
std::cout << ( isEmpty(list1) ) ;
// we make another data element and update the list again
D two = 2 ;
L b;
b.dat = two ;
b.nxt = nullptr ;
Program continues on the next page →
L* elem2 ;
elem2 = &b ;
list1 = insert( elem2 , list1 ) ; // Note: this list now "looks like" 2:1:null
// now we make the 2nd list, and let another pointer point to it:
L* list2 = create();
std::cout << ( isEmpty(list2) ) ;
// we make another data element
D three = 3 ;
L c;
c.dat = three ;
c.nxt = nullptr ;
L* elem3 ;
elem3 = &c ;
// we update this list and check whether it is still empty
list2 = insert( elem3 , list2 ) ;
std::cout << ( isEmpty(list2) ) ;
// we make yet another data element and update the list again
D four = 4 ;
L d;
d.dat = four ;
d.nxt = nullptr ;
L* elem4 ;
elem4 = &d ;
list2 = insert( elem4 , list2 ) ; // Note: this list now "looks like" 4:3:null
According to our Algorithm of above we now compose our long list as:
L* list3 = append( list1 , list2 ) ; // Note: this list now "looks like" 2:1:4:3:null
// If we did everything correctly up to now, then the 3rd data of list3 should be: 4 !
// As in the COS151 Aegrotat Exam we get the 3rd element with head(tail(tail(L)))
// Let us test this now with:
D* result = head( tail( tail( list3 ))) ;
std::cout << ( *result ) ; // ...and indeed the screen output is the number 4 :)
// Just for fun let us also print the list's other element as follows:
std::cout << ( *(head(list3)) ) ;
// 1st element of list3 = number 2
std::cout << ( *(head(tail(list3))) ) ;
// 2nd element of list3 = number 1
std::cout << ( *(head(tail(tail(tail(list3))))) ) ; // 4th element of list3 = number 3
return 0 ; } // end of the C++ main program
In Summary and Conclusion :
• We have recapitulated the Abstract Algebraic Data Type LIST(D) from course COS151.
• With C++ we have implemented a definition of D: in our example as integer numbers.
• With C++ we have implemented the specific operations by which the Data Type was defined
• For the purpose of the implementation we have used several programming concepts such as
typedef, struct, *pointers, &addresses, as well as functions with recursion.
Thus we are now well prepared for the next course module, COS110, in the next semester :)
In COS110 you will see how data "plus" their characteristic operations get "packaged" into objects !
________________________________________________________________________________________________________________________________________________________________
All the best wishes for your next semester, to all of you, from: Prof.G.