Você está na página 1de 10

Ch.

8: Abstract Datatypes

Plan

Ch.8: Abstract Datatypes

Plan

Chapter 8 Abstract Datatypes


An abstract data type is a description of a common representation of data. Abstract data types are one of the most important concepts in all programming, because they allow building representations for complex data from simpler parts. Also, looking at abstract data types gives us an opportunity to review concepts such as functions on lists, datatypes, polymorphism, currying, higher-order functions and show some the functional implementation of some algorithms. A simple example: A counter. We need a few operations: make_counter : () -> counter inc : counter -> counter dec : counter -> counter To keep things simple, assume that when a counter reaches zero, it stays at zero if we keep decrementing it. More operations: is_zero : counter -> bool = : counter * counter -> bool Lets assume that counters can be compared for equality.

Properties of counters
We might also require that our representation should satisfy some obvious properties. The following should hold for any counters c1 and c2: is_zero(make_counter()) not is_zero(inc c) c1 = c2 iff inc(c1) = inc(c2) c1 = c2 implies that dec(c1) = dec(c2) c1 = dec(inc(counter)) Questions: do the requirements above allow us to show that inc(c1) = c1 holds for no counter c1? does the requirements above state everything we would like to be true for counters?

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.1

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.2

Ch.8: Abstract Datatypes

Plan

Ch.8: Abstract Datatypes

Plan

Implementing counters
type counter = int fun fun fun fun make_counter () = 0 inc c = c+1 dec c = if c = 0 then 0 else c-1 is_zero c = c = 0

Counters as lists
We could do something more imaginative: type counter = unit list fun fun fun fun make_counter () = [] inc c = ()::[] dec c = if c = [] then [] else tl c is_zero c = null c

(Check that this representation satises the requirements)

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.3

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.4

Ch.8: Abstract Datatypes

Plan

Ch.8: Abstract Datatypes

Plan

A datatype for counters


Something even more imaginative: datatype counter = EmptyCounter | UnitCounter of counter fun fun fun | fun | make_counter () = EmptyCounter inc c = UnitCounter c dec (EmptyCounter) = EmptyCounter dec (UnitCounter c) = c is_zero (EmptyCounter) = true is_zero (UnitCounter c) = false

Odd integers for counters


type counter = int fun fun fun fun make_counter () = 1 inc c = c+2 dec c = if c = 1 then 1 else c-2 is_zero c = c = 1

Now, note that none of the representations oer any mechanism for protecting the data from misuse.

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.5

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.6

Ch.8: Abstract Datatypes

Plan

Ch.8: Abstract Datatypes

Plan

Why abstract datatypes? Useful to make a datatype abstract when the implementation of the datatype is complex, there are many ways to implement the datatype, or you want to keep the datatype separate from the rest of the code. The datatype represents a natural abstraction. Abstract datatypes are often a good way to split a program into parts that can be understood individually. What is an abstraction (in Computer Science)?

What is an abstraction (in Computer Science)? A way to introduce new concepts that are meaningful to humans. Examples: les, data structures, procedure calls, other programming constructs. (We think of les as something real, but les dont exist, they are just a bunch of bits on a hard drive. Come to think of it, bits dont exist either, they are just magnetic uctuations on the surface of the disk platters.)

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.7

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.8

Ch.8: Abstract Datatypes

8.1. An abstract datatype for stacks

Ch.8: Abstract Datatypes

8.1. An abstract datatype for stacks

8.1. An abstract datatype for stacks


Stacks of objects of type : stack Operations

Formal semantics

value emptyStack TYPE: stack VALUE: the empty stack function isEmptyStack S TYPE: stack bool PRE: (none) POST: true if S is empty false otherwise function push v S TYPE: stack stack PRE: (none) POST: the stack S with v added as new top element function top S TYPE: stack PRE: S is non-empty POST: the top element of S function pop S TYPE: stack stack PRE: S is non-empty POST: the stack S without its top element

isEmptyStack emptyStack = true v,S : isEmptyStack (push v S) = false top emptyStack = ... error ... v,S : top (push v S) = v pop emptyStack = ... error ... v,S : pop (push v S) = S

Question: Do the properties above state everything we need to know about stacks? Is there any other property you would like to add?

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.9

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.10

Ch.8: Abstract Datatypes

8.2. Realisation of the stack abstract datatype

Ch.8: Abstract Datatypes

8.2. Realisation of the stack abstract datatype

8.2. Realisation of the stack abstract datatype


Version 1
Representation of a stack by a list :

Version 2
Denition of a new constructed type using the list type:

datatype stack = Stack of list REPRESENTATION CONVENTION: the head of the list is the top of the stack, the 2nd element of the list is the element below the top, etc

type stack = list REPRESENTATION CONVENTION: the head of the list is the top of the stack, the 2nd element of the list is the element below the top, etc
Realisation of the operations (stack1.sml)

Realisation of the operations

val emptyStack = Stack [ ] fun isEmptyStack (Stack S) = (S = [ ]) fun push v (Stack S) = Stack (v::S) fun top (Stack [ ]) = error "top: empty stack" | top (Stack (x::xs)) = x fun pop (Stack [ ]) = error "pop: empty stack" | pop (Stack (x::xs)) = Stack xs

val emptyStack = [ ] fun isEmptyStack S = (S = [ ]) fun push v S = v::S fun top [ ] = error "top: empty stack" | top (x::xs) = x fun pop [ ] = error "pop: empty stack" | pop (x::xs) = xs

The operations are now only dened for stacks This realisation does not force the usage of the stack type The operations can also be used with objects of type list, even if they do not represent stacks! It is possible to access the elements of the stack without using the operations specied above: no encapsulation! It is still possible to access the elements of the stack without using the operations specied above, namely by pattern matching

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.11

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.12

Ch.8: Abstract Datatypes

8.2. Realisation of the stack abstract datatype

Ch.8: Abstract Datatypes

8.2. Realisation of the stack abstract datatype

An abstract datatype (stack2.sml) Objective: encapsulate the denition of the stack type and its operations in a parameterised abstract datatype

abstype a stack = Stack of a list with val emptyStack = Stack [ ] fun isEmptyStack (Stack S) = (S = [ ]) fun push v (Stack S) = Stack (v::S) fun top (Stack [ ]) = error "top: empty stack" | top (Stack (x::xs)) = x fun pop (Stack [ ]) = error "pop: empty stack" | pop (Stack (x::xs)) = Stack xs end

abstype a stack = Stack of a list with . . . ; type a stack val a emptyStack = - : a stack val a isEmptyStack = fn : a stack -> bool ... push 1 (Stack [ ]) ; Error: unbound variable or constructor: Stack push 1 emptyStack ; val it = - : int stack

It is impossible to compare two stacks:

emptyStack = emptyStack ; Error: operator and operand dont agree [equality type required]

The stack type is an abstract datatype (ADT) The concrete representation of a stack is hidden An object of the stack type can only be manipulated via the functions dened in its ADT declaration The Stack constructor is invisible outside the ADT It is now impossible to access the representation of a stack outside the declarations of the functions of the ADT The parameterisation allows the usage of stacks of integers, reals, strings, integer functions, etc, from a single denition!

It is impossible to see the contents of a stack without popping its elements, so let us add a visualisation function:

function showStack S TYPE: stack list PRE: (none) POST: the representation of S in list form, with the top of S as head, etc abstype a stack = Stack of a list with ... fun showStack (Stack S) = S end
The result of showStack is not of the stack type One can thus not apply the stack operations to it

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.13

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.14

Ch.8: Abstract Datatypes

8.2. Realisation of the stack abstract datatype

Ch.8: Abstract Datatypes

8.2. Realisation of the stack abstract datatype

Version 3
Denition of a recursive new constructed type:

Comment on abstract data types


By now we have all realized that the stack can be represented as a list with the elements in reverse order. Lists are good when we add/remove/access elements from the start, but not when we apply the same operations to the end of the list. By using an abstract data type for a subproblem, we can work on solving a problem even if we have not yet solved all subproblems. If we later nd that the solution to the subproblem is unsatisfactory we can change it. Sometimes the subproblem may be so hard that we do not want to think of it while we work with the main problem (we need to be condent that we can solve it, of course). On the other hand, I have often found that discovering the right abstractions is harder than implementing them. Badly selected abstractions may lead to unnecessarily complex programs. This may be an argument against introducing abstractions early.

datatype stack = EmptyStack | >> of stack inx >> EXAMPLE: EmptyStack >> 3 >> 5 >> 2 represents the stack with top 2 REPRESENTATION CONVENTION: the right-most value is the top of the stack, its left neighbour is the element below the top, etc
An abstract datatype (stack3.sml)

abstype a stack = EmptyStack | >> of a stack a with inx >> val emptyStack = EmptyStack fun isEmptyStack EmptyStack = true | isEmptyStack (S>>v) = false fun push v S = S>>v fun top EmptyStack = error "top: empty stack" | top (S>>v) = v fun pop EmptyStack = error "pop: empty stack" | pop (S>>v) = S fun showStack EmptyStack = [ ] | showStack (S>>v) = v :: (showStack S) end
We have thus dened a new list constructor, but with access to the elements from the right !

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.15

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.16

Ch.8: Abstract Datatypes

8.3. An abstract datatype for FIFO queues

Ch.8: Abstract Datatypes

8.3. An abstract datatype for FIFO queues

8.3. An abstract datatype for FIFO queues


First-in rst-out (FIFO) queues of objects of type : queue Addition of elements to the rear (tail ) Deletion of elements from the front (head )

function dequeue Q TYPE: queue queue PRE: Q is non-empty POST: the queue Q without its head element function showQueue Q TYPE: queue list PRE: (none) POST: the representation of Q in list form, with the head of Q as head, etc

Operations

value emptyQueue TYPE: queue VALUE: the empty queue function isEmptyQueue Q TYPE: queue bool PRE: (none) POST: true if Q is empty false otherwise function enqueue v Q TYPE: queue queue PRE: (none) POST: the queue Q with v added as new tail element function head Q TYPE: queue PRE: Q is non-empty POST: the head element of Q

Formal semantics

isEmptyQueue emptyQueue = true v,Q : isEmptyQueue (enqueue v Q) = false head emptyQueue = ... error ... v,Q : head (enqueue v Q) = if isEmptyQueue Q then v else head Q dequeue emptyQueue = ... error ... v,Q : dequeue (enqueue v Q) = if isEmptyQueue Q then emptyQueue else enqueue v (dequeue Q)

Question: Do the properties above state everything we need to know about stacks? Is there any other property you would like to add?

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.17

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.18

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

8.4. Realisation of the queue abstract datatype


Version 1
Representation of a FIFO queue by a list :

Version 2
Representation of a FIFO queue by a pair of lists :

type queue = list REPRESENTATION CONVENTION: the head of the list is the head of the queue, the 2nd element of the list is behind the head of the queue, and so on, and the last element of the list is the tail of the queue
Example: the queue
head 3 8 7 5 0 tail 2

datatype queue = Queue of list list REPRESENTATION CONVENTION: the term Queue ([x1, x2, . . . , xn], [y1, y2, . . . , ym ]) represents the queue
head tail

x1 x2

. . . xn ym

...

y2 y1

REPRESENTATION INVARIANT: (see next slide)

It is now possible to enqueue in (1) time It is still possible to dequeue in (1) time, but only if n 1 What if n = 0 while m > 0?! The same queue can thus be represented in dierent ways How to test the equality of two queues?

is represented by the list [3,8,7,5,0,2] Exercises Realise the queue ADT using this representation What is the time complexity of enqueuing an element? What is the time complexity of dequeuing an element?

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.19

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.20

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Normalisation Objective: avoid the case where n = 0 while m > 0 When this case appears, transform (or: normalise) the representation of the queue: transform Queue ([ ], [y1, . . . , ym ]) with m > 0 into Queue ([ym , . . . , y1 ], [ ]), which indeed represents the same queue We thus have:

REPRESENTATION INVARIANT: a non-empty queue is never represented by Queue ([ ], [y1, . . . , ym])

function normalise Q TYPE: queue queue PRE: (none) POST: if Q is of the form Queue ([ ], [y1, . . . , ym ]) then Queue ([ym, . . . , y1 ], [ ]) else Q
Realisation of the operations (queue2.sml) Construction of an abstract datatype: the normalise function may be local to the ADT, as it is only used for realising some operations on queues

abstype a queue = Queue of a list a list with val emptyQueue = Queue ([ ],[ ]) fun isEmptyQueue (Queue ([ ],[ ])) = true | isEmptyQueue (Queue (xs,ys)) = false fun head (Queue (x::xs,ys)) = x | head (Queue ([ ],[ ])) = error "head: empty queue" | head (Queue ([ ],y::ys)) = error "head: non-normalised queue" local fun normalise (Queue ([ ],ys)) = Queue (rev ys,[ ]) | normalise Q = Q in fun enqueue v (Queue (xs,ys)) = normalise (Queue (xs,v::ys)) fun dequeue (Queue (x::xs,ys)) = normalise (Queue (xs,ys)) | dequeue (Queue ([ ],[ ])) = error "dequeue: empty queue" | dequeue (Queue ([ ],y::ys)) = error "dequeue: non-norm. queue" end fun showQueue (Queue (xs,ys)) = xs @ (rev ys) fun equalQueues Q1 Q2 = (showQueue Q1 = showQueue Q2) end

Why do the head and dequeue functions not normalise the queue instead of stopping the execution with an error? The normalisation and representation invariant are hidden in the realisation of the abstract datatype On average, the time of enqueuing and dequeuing is (1) This representation is thus very ecient!

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.21

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.22

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

How do I justify the constant time complexity? A simple amortized analysis Suppose enQueue an element costs 1 krona. deQueue an elements costs 1 krona if we do not need to reverse 1 + n krona if we reverse a list of length n Make the cost of adding an element to the queue two kronor, and the cost of removal 1 krona Each time we add an element we use 1 krona and keep 1. When deQueue need to reverse a listthere is money in the savings account to pay for it. Conclusion: a single deQueue operation may be costly but a sequence of n enQueue and deQueue (starting with an empty queue) has cost proportional to n. The average cost of deQueue has a constant bound.

Tables Some operations: empty_table create an empty table insert k v t insert a key k with associated value v into the table t lookup k t look for the key k in the table t, if there is a key k with corresponding value v, return SOME v else return NONE to_list t return a list of key-value pairs

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.23

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.24

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

A simple implementation of tables: association lists val empty_table = [] fun insert k v t = (k,v)::t fun lookup k [] = NONE | lookup k ((k,v)::t) = if k=k then SOME v else lookup k t fun to_list t = t fun from_list [] = empty_table | from_list ((k,v)::xs) = insert k v (from_list xs)

Notes on association lists every computer scientist should know what an alist is! inecient (why?) what is their advantage? the list may contain duplicate keys. Does not aect search, but result of to_list may be unsatisfactory polymorphic in keys and values, but there is a requirement on keys A better to_list fun to_list t = let fun tl [] acc = rev acc | tl ((k,v)::xs) acc = (case lookup k acc of NONE => tl xs (insert k v acc) | SOME _ => tl xs acc) in tl t [] end

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.25

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.26

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Association lists, test run Test run: val val val val val val val empty_table = [] : a list insert = fn : a -> b -> (a * lookup = fn : a -> (a * b) to_list = fn : a -> a to_list = fn : (a * b) list from_list = fn : (a * b) list it = () : unit b) list -> (a * b) list list -> b option -> (a * b) list -> (a * b) list

Functional tables Idea: represent a function as a table (see also previous lecture). First, two types for the operation and result: datatype (a, b) table_op = Insert of (a * b) | Lookup of a | ToList datatype (a, b) result = Table of (a,b) table_op -> (a, b) result | Found of b option | List of (a*b) list Empty tables fun empty_table (Insert(k,v)) = Table (non_empty_table k v empty_table) | empty_table (Lookup k) = Found NONE | empty_table ToList = List [] Non-empty tables fun non_empty_table k v t (Insert(k,v)) = Table (non_empty_table k v (non_empty_table k v t)) | non_empty_table k v t (Lookup k) = if k=k then Found (SOME v) else t (Lookup k) | non_empty_table k v t ToList = let val List l = (t ToList) in List ((k,v)::l) end

- from_list [("one", 1),("two", 2), ("three", 3), ("four", 4)]; val it = [("one",1),("two",2),("three",3), ("four",4)] : (string * int) list - from_list [("one", 1),("two", 2), ("three", 3), ("four", 4)]; val it = [("one",1),("two",2),("three",3), ("four",4)] : (string * int) list - insert "four" 5 it; val t = [("four",5),("one",1),("two",2),("three",3), ("four",4)] : (string * int) list - to_list t; val it = [("four",5),("one",1),("two",2),("three",3), ("four",4)] : (string * int) list - to_list t; val it = [("four",5),("one",1),("two",2), ("three",3)] : (string * int) list -

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.27

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.28

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Functional tables, interface fun insert k v t = let val Table t = in t end

Tables as binary search trees datatype b bsTree = Void | Bst of int * b * b bsTree * b bsTree val empty_table = Void fun insert k v Void = Bst(k,v,Void,Void) | insert k v (Bst(key,value,L,R)) = if k = key then Bst(k,v,L,R) else if k < key then Bst(key,value, (insert k v L), R) else Bst(key, value, L, (insert k v R)) fun lookup k Void = NONE | lookup k (Bst(key,value,L,R)) = if k = key then SOME value else if k < key then lookup k L else lookup k R

t (Insert(k,v))

fun lookup k t = let val Found f = t (Lookup k) in f end fun to_list t = let val List l = t ToList in l end

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.29

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.30

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Functional tables, conversion to lists A simple conversion fun to_list Void = [] | to_list (Bst(key,value,L,R)) = (to_list L) @ [(key, value)] @ (to_list R)

But our binary search trees are not polymorphic Use of integer comparison means that only integers can be used as keys. Solutions: allow extra parameter: insert order key val t store order in data structure (how?) dene higer-order function that creates specialized functions

A more ecient one fun to_list t = let fun t_l Void acc = acc | t_l (Bst(key,value,L,R)) acc = t_l L ((key, value)::(t_l R acc)) in t_l t [] end

val (empty_table, insert, lookup, to_list) = make_bst_adt (String.<) (this is easier than it looks!)

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.31

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.32

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Sequences empty_seq create empty sequence % create singleton sequence $ concatenate two sequences (so that given sequences s1 and s2 we obtain a new sequence s1 $ s2) seqmap is like map on lists seq2list converts a sequence to a list

Sequences, the obvious solution val empty_seq = [] fun % x = [x] nonfix $ val $ = op @ infix 2 $ val seqmap = List.map val seq2list = fn x => x Lets look at the types val empty_seq = [] : a list val % = fn : a -> a list val $ = fn : a list * a list -> a list val seqmap = fn : (a -> b) -> a list -> b list val seq2list = fn : a -> a Not very interesting. Also, not very ecient if we do a lot of concatenations.

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.33

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.34

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Sequences as trees infix 2 $ datatype a seq = $ of a seq * a seq | % of a | empty_seq fun seqmap f (s1 $ s2) = (seqmap f s1 $ seqmap f s2) | seqmap f (% l) = (f l) | seqmap f empty_seq = empty_seq fun seq2list s = let fun f (s1 $ s2, l) = f(s1, f(s2, l)) | f (% a, l) = a :: l | f (empty_seq, l) = l in f (s, []) end

Polynomials Examples x+1 x*x+100*x 42 x^100 Some operations constant c create a polynomial representing a constant timesX p multiply a polynomial with x eval p v determine the value of the polynomial, given that x=v add p1 p2 add two polynomials

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.35

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.36

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Polynomials as lists Example: the polynomial 2x4 + 5x3 + x2 + 3 can be represented by the list [3,0,1,5,2] In general: the list [a0, a1, . . . , an] with an = 0 represents the polynomial P n (x) = an xn + + a1 x + a0 We assume integer coecients and natural-number powers type poly = int list fun constant c = [c]

Sparse polynomials
What if a lot of coecients are zero?! Example: 3x27 + 4x5 + 3x2 In the preceding representation: High memory consumption High run time of the operations (many evaluation steps) We need a better representation! Representation of sparse polynomials

fun timesX p = 0::p fun eval [] v = 0 | eval (a::p) v = (eval p v) * v + a fun add p1 [] = p1 | add [] p2 = p2 | add (a::p1) (b::p2) = (a+b) :: add p1 p2

Example: the polynomial 3x27 + 4x5 + 3x2 can be represented by the list [(2,3), (5,4), (27,3)] In general: the list [(k1, c1), . . . , (km, cm )] with: ci = 0 for 1 i m ki 0 for 1 i m ki < ki+1 for 1 i < m represents the polynomial cm xkm + + c1xk1

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.37

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.38

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

Ch.8: Abstract Datatypes

8.4. Realisation of the queue abstract datatype

type poly = (int*int) list fun constant c = [(0,c)] fun timesX [] = [] | timesX ((k,c)::p) = (k+1,c)::timesX p fun expo x 0 = 1 | expo x n = x * expo x (n-1) fun eval [] v = 0 | eval ((k, c)::p) v =

Sparse polynomials, questions When, if ever, is the sparse polynomial representation more ecient than the rst? Is it meaningful to compare polynomials for equality, i.e., will one polynomial always have the same representation? Does the function add remind you of any other function you have seen? There is a little bug in the sparse polynomials. Can you nd it? Can you see any way to improve performance?

c*(expo v k)+(eval p v)

fun add [] p = p | add p [] = p | add ((k1,c1)::p1) ((k2, c2)::p2) = if k1 < k2 then (k1,c1)::(add p1 ((k2, c2)::p2)) else if k2 < k1 then (k2, c2)::(add ((k1,c1)::p1) p2) else let val c = c1+c2 in if c = 0 then add p1 p2 else (k1,c)::(add p1 p2) end

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.39

Sven-Olof Nystr om/IT Dept/Uppsala University

FP

8.40

Você também pode gostar