ABSTRACT DATA TYPES Reference Tremblay and Cheston: 6.1 - 6.3, 7.2, 9.2, 11.1.1 One of the keys to design and development of large software packages is data abstraction i.e., abstract specification of methods during the design of a class; specifies the type/class behaviour without giving the implementation. Why develop the ADT? Specify behaviour of a type before its implementation as a class. Other types are designed based on this behaviour. Facilitates independent development of different types by different individuals. Postpones development of details that initially are unnecessary. Programming language independent. Precise. Why hide the implementation (even if it exists)? The current type is the supplier in a client-supplier relationship. Note that a client is another module that uses the type being defined. Minimizes coupling: clients only know the ADT, not the implementation, of the current type/class Implementation may change, but don’t want it to affect client classes A client cannot make changes to the implementation that cause errors What needs to be specified for an Abstract Data Type? name of the type data types needed the one being specified others used to define the present type Each of them must be already defined by its own ADT mathematically defined e.g., sets, integers, reals, pairs, triples, etc. built into the language signature for each operation of the new type precondition for each operation semantics meaning of each operation what does it do? Techniques to specify an ADT Axiomatic Constructive Postcondition Each technique uses a different approach to specify the semantics. Only the axiomatic technique will be discussed. Student Abstract Data Type I. Name Student II. Sets ?? III. Signatures ?? What features are the students going to have? name student number ability to change name Student Abstract Data Type I. Name Student II. Sets S : set of all students N : set of all names K : set of all student numbers III. Signatures (using object oriented notation) newStudent: N x K S S.getName: N S.getNumber: K S.setName: N S Note that all operations are defined to be functions with no side effects. Operations that would normally change an object, like setName, are defined to be functions that return a new object. IV. Preconditions IV. Preconditions For all s S, n N, n2 N, k K pre-newStudent(n, k) ::= true pre-s.getName() ::= true pre-s.getNumber() ::= true pre-s.setName(n2) ::= true V. Semantics Axiomatic approach (a) specify a minimum set of operations that can be used to construct an arbitrary instance, call them the build operations newStudent (b) for each other operation (the non-build operations) when this operation is invoked on an arbitrary instance (built by the build operations) define the result for this operation (usually using the parameters of the build operation, and sometimes using build operations) V. Semantics (a) Build operations newStudent (b) Axioms newStudent(n, k).getName = n newStudent(n, k).getNumber = k newStudent(n, k).setName(n2) = newStudent(n2, k) Simple List ADT I. Name List<G> II. Sets L = set of all lists containing items of type G G = set of all items that can be placed in the list B = {true, false} III. Signatures newList<G>: L L.isEmpty: B L.insertFirst: G L L.firstItem: ↛ G L.deleteFirst: ↛ L IV. Preconditions For all l L, g G pre-newList<G>() ::= true pre-l.isEmpty() ::= true pre-l.insertFirst(g) ::= true IV. Preconditions For all l L, g G pre-newList<G>() ::= true pre-l.isEmpty() ::= true pre-l.insertFirst(g) ::= true pre-l.firstItem() ::= not l.isEmpty() pre-l.deleteFirst() ::= not l.isEmpty() V. Semantics (a) Build operations (b) Axioms V. Semantics (a) Build operations newList insertFirst Note that an arbitrary list has one of two forms: newList() or l.insertFirst(g), for some existing list l and some item g V. Semantics (a) Build operations newList insertFirst (b) Axioms newList().isEmpty() = true l.insertFirst(g).isEmpty() = false V. Semantics (a) Build operations newList insertFirst (b) Axioms newList().isEmpty() = true l.insertFirst(g).isEmpty() = false l.insertFirst(g).firstItem() =g l.insertFirst(g).deleteFirst() = l alternate notation when there is more than one case (if expression): l.isEmpty() = if l = newList() then true else false ADT for a Queue with a bounded size I. Name Queue<G> II. Sets Q = set of all queues with items from G G = set of all items to be placed in the queue B = {true, false} N = set of non-negative integers III. Signatures newQueue<G>: N Q Q.isEmpty: B Q.isFull: B Q.insert: G ↛ Q Q.item: ↛ G // the item that has been in the queue the longest Q.deleteItem: ↛ Q // the current item III. Preconditions For all q Q, g G pre-q.insert(g) ::= not q.isFull() pre-q.item() ::= not q.isEmpty pre-q.deleteItem() ::= not q.isEmpty() IV. Semantics (a) Build operations newQueue, insert (b) Semantics q.isEmpty() = if q = newQueue<G>(n) then true else false What is the definition/semantics of the item() operation? IV. Semantics (a) Build operations newQueue, insert (b) Semantics q.isEmpty() = if q = newQueue<G>(n) then true else false Note that insertion is at the end of the queue, while the current item is at the front q.insert(g).item() = if q.isEmpty() then g else q.item() What is the definition/semantics of the deleteItem() operation? rule 1 rule 2 Note that insertion is at the end of the queue, while the first item is the one to be deleted q.insert(g).deleteItem() = if q.isEmpty() then q else q.deleteItem().insert(g) Trace: Notation: draw a queue as an array with the front item of the queue at the front of the array. Suppose that the queue has capacity 6. [2, 7, 4, , , ].deleteItem() = newQueue<int>(6).insert(2).insert(7).insert(4).deleteItem() using the ADT notation = (newQueue<int>(6).insert(2).insert(7)) .insert(4).deleteItem() parenthesis = (newQueue<int>(6).insert(2).insert(7)) .deleteItem().insert(4) used rule 2 = (newQueue<int>(6).insert(2)) .insert(7).deleteItem().insert(4) parenthesis = (newQueue<int>(6).insert(2)) .deleteItem().insert(7). insert(4) used rule 2 = (newQueue<int>(6)) .insert(2).deleteItem().insert(7). insert(4) parenthesis = newQueue<int>(6).insert(7). insert(4) used rule 1 = [7, 4, , , , ] Sometimes to define an operation, it is necessary/helpful to define other operations. For example, to know whether the queue is full or not, it is necessary to determine its capacity and compare that to the number currently in the queue. Hence, we need to have operations to determine the capacity and current number in the queue. Signatures: Q.capacity: N Q.count: N Preconditions: none Semantics: newQueue<G>(n).capacity() = n q.insert(g).capacity() = q.capacity() newQueue<G>(n).count() = 0 q.insert(g).count() = 1 + q.count() or q.count() = if q.isEmpty() then 0 else 1 + q.deleteItem().count() q.isFull() = if q.capacity() = q.count() then true else false