3 Abstract Data Types

advertisement
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
Download