Chapter 8 Properties of counters Abstract Datatypes We might also - - PDF document

chapter 8 properties of counters abstract datatypes
SMART_READER_LITE
LIVE PREVIEW

Chapter 8 Properties of counters Abstract Datatypes We might also - - PDF document

Ch.8: Abstract Datatypes Ch.8: Abstract Datatypes Plan Plan Chapter 8 Properties of counters Abstract Datatypes We might also require that our representation should satisfy some obvious properties. The following should hold for any counters


slide-1
SLIDE 1

Ch.8: Abstract Datatypes Plan

Properties of counters

We might also require that our representation should satisfy some

  • bvious 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¨

  • m/IT Dept/Uppsala University

FP

8.2

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

Let’s assume that counters can be compared for equality.

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.1

Ch.8: Abstract Datatypes Plan

Counters as lists

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.4

Ch.8: Abstract Datatypes Plan

Implementing counters

type counter = int fun make_counter () = 0 fun inc c = c+1 fun dec c = if c = 0 then 0 else c-1 fun is_zero c = c = 0 (Check that this representation satisfies the requirements)

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.3

slide-2
SLIDE 2

Ch.8: Abstract Datatypes Plan

Odd integers for counters

type counter = int fun make_counter () = 1 fun inc c = c+2 fun dec c = if c = 1 then 1 else c-2 fun is_zero c = c = 1 Now, note that none of the representations offer any mechanism for protecting the data from misuse.

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.6

Ch.8: Abstract Datatypes Plan

A datatype for counters

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.5

Ch.8: Abstract Datatypes Plan

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.8

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)?

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.7

slide-3
SLIDE 3

Ch.8: Abstract Datatypes 8.1. An abstract datatype for stacks

‘Formal’ semantics 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¨

  • m/IT Dept/Uppsala University

FP

8.10

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.9

Ch.8: Abstract Datatypes 8.2. Realisation of the stack abstract datatype

Version 2

Definition 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 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

  • The operations are now only defined for stacks
  • It is still possible to access the elements of the stack

without using the operations specified above, namely by pattern matching

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.12

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: 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) 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

  • 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 specified above: no encapsulation!

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.11

slide-4
SLIDE 4

Ch.8: Abstract Datatypes 8.2. Realisation of the stack abstract datatype

  • 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 don’t agree [equality type required] 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¨

  • m/IT Dept/Uppsala University

FP

8.14

Ch.8: Abstract Datatypes 8.2. Realisation of the stack abstract datatype

An abstract datatype (stack2.sml) Objective: encapsulate the definition 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

  • 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 defined in its ADT declaration

  • The Stack constructor is invisible outside the ADT
  • It is now impossible to access the representation of a stack
  • utside 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 definition!

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.13

Ch.8: Abstract Datatypes 8.2. Realisation of the stack abstract datatype

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 find 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

  • f it while we work with the main problem (we need to be confident 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.

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.16

Ch.8: Abstract Datatypes 8.2. Realisation of the stack abstract datatype

Version 3

Definition of a recursive new constructed type: datatype α stack = EmptyStack | >> of α stack ∗ α infix >> 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 infix >> 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 defined a new list constructor, but with access to the elements from the right!

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.15

slide-5
SLIDE 5

Ch.8: Abstract Datatypes 8.3. An abstract datatype for FIFO queues

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 ‘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¨

  • m/IT Dept/Uppsala University

FP

8.18

Ch.8: Abstract Datatypes 8.3. An abstract datatype for FIFO queues

8.3. An abstract datatype for FIFO queues

First-in first-out (FIFO) queues of objects of type α: α queue

  • Addition of elements to the rear (tail)
  • Deletion of elements from the front (head)

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.17

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

Version 2

Representation of a FIFO queue by a pair of lists: datatype α queue = Queue of α list ∗ α list REPRESENTATION CONVENTION: the term Queue ([x1, x2, . . . , xn], [y1, y2, . . . , ym]) represents the queue head tail x1 xn ym y1 . . . x2 . . . y2 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 different ways
  • How to test the equality of two queues?

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.20

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: 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 8 7 2 5 3 head tail 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¨

  • m/IT Dept/Uppsala University

FP

8.19

slide-6
SLIDE 6

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

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 efficient!

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.22

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.21

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

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¨

  • m/IT Dept/Uppsala University

FP

8.24

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 list—there is money in the savings account to pay for it. Conclusion: a single deQueue operation may be costly – but a sequence

  • f n enQueue and deQueue (starting with an empty queue) has cost

proportional to n. The average cost of deQueue has a constant bound.

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.23

slide-7
SLIDE 7

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

Notes on association lists

  • every computer scientist should know what an alist is!
  • inefficient (why?)
  • what is their advantage?
  • the list may contain duplicate keys. Does not affect 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¨

  • m/IT Dept/Uppsala University

FP

8.26

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)

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.25

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.28

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

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

  • 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¨
  • m/IT Dept/Uppsala University

FP

8.27

slide-8
SLIDE 8

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.30

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

Functional tables, interface fun insert k v t = let val Table t’ = t (Insert(k,v)) in t’ end 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¨

  • m/IT Dept/Uppsala University

FP

8.29

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

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?)
  • define higer-order function that creates specialized functions

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.32

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) A more efficient 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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.31

slide-9
SLIDE 9

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

Sequences, the obvious solution val empty_seq = [] fun % x = [x] nonfix $ val $ = op @ infix 2 $ val seqmap = List.map val seq2list = fn x => x Let’s 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 efficient if we do a lot of concatenations.

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.34

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.33

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

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¨

  • m/IT Dept/Uppsala University

FP

8.36

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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.35

slide-10
SLIDE 10

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

Sparse polynomials

What if a lot of coefficients 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 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 cmxkm + · · · + c1xk1

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.38

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 Pn(x) = anxn + · · · + a1x + a0 We assume integer coefficients and natural-number powers type poly = int list fun constant c = [c] 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

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.37

Ch.8: Abstract Datatypes 8.4. Realisation of the queue abstract datatype

Sparse polynomials, questions

  • When, if ever, is the sparse polynomial representation more efficient

than the first?

  • 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 find it?
  • Can you see any way to improve performance?

Sven-Olof Nystr¨

  • m/IT Dept/Uppsala University

FP

8.40

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 = 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¨

  • m/IT Dept/Uppsala University

FP

8.39