Вы находитесь на странице: 1из 33

15-150 Fall 2014

Stephen Brookes

Lecture 11

today
Generalizing the subset-sum problem

Developing specs and code together

A lesson in program design

the problem
Given a non-negative integer n,

a list of positive integers L,


and a constraint p : int list -> bool

Is there a sublist of L that satisfies p


and adds up to n?

p A may loop
if A contains
a negative integer

background
fun sublists [ ] = [ [ ] ]!
| sublists (x::R) = !
let !
val S = sublists R !
in !
S @ map (fn L => x::L) S !
end
fun exists p [ ] = false!
| exists p (x::R) = p(x) orelse exists p R

fun sum L = foldr (op +) 0 L!

sublists L

evaluates to a

list of all sublists of L

with L last

and no repetitions

If p is total,

exists p [x1,xn]

evaluates to true

iff p(xi) = true

for some i

with 1 i n

badchange
A non-recursive function that returns a boolean

fun badchange (n, L) p = !


exists (fn A => sum A = n andalso p A) (sublists L)

badchange : int * int list -> (int list -> bool) -> bool
REQUIRES p is total

this spec for badchange



is correct, because of the

the (proven) specs of

sublist, exists

ENSURES badchange (n, L) p = true



if there is a sublist A of L with

sum A = n and p A = true
badchange (n, L) p = false

otherwise

critique
badchange (300, [1,2,3,...,24]) (fn _ => true)
==================>* true
Generate-and-test, brute force search

tests them sequentially

generates the list of all sublists


(length is 224)
only the final sublist will work!
1 + 2 + + 24 = 300

evenworsechange
An even worse* function with the same spec

fun evenworsechange (n, L) p = !


exists (fn A => p A andalso sum A = n) (sublists L)

Again, wasteful to build list of all sublists



*Even worse when p(A) is expensive to evaluate

here we check p(A) on all sublists,

even when the value of sum(A) is incorrect

specification
change : int * int list -> (int list -> bool) -> bool
REQUIRES p is total, n0, L a list of positive integers
ENSURES change (n, L) p = true

if there is a sublist A of L with

sum A = n and p A = true

change (n, L) p = false, otherwise
+ must be faster!

a better strategy
Avoid building the list of all sublists of L

only call p on sublists with the correct sum

Deal with special cases first

n = 0

n > 0, L = [ ]

For n > 0, L = x::R, use recursion...
the spec suggests

this might be possible!

change
A recursive function that returns a boolean

fun change (0, L) p =! p [ ]


| change (n, [ ]) p =! false
| change (n, x::R) p =!
!if x <= n !
!
then change
(n-x, R) (fn A => p(x::A))
!orelse
change
(n, R) p
!
else change (n, R) p

change (300, [1,2,3,...,24]) (fn _ => true)


=> true
(very fast)

type check
change : int * int list -> (int list -> bool) -> bool

fun change (0, L) p =! p [ ]


| change (n, [ ]) p =! false
| change (n, x::R) p =!
!if x <= n !
!
then change
(n-x, R) (fn A => p(x::A))
!orelse
change
(n, R) p
!
else change (n, R) p
For each clause,
if arguments and recursive calls
have correct types, so does right-hand side

requirements check
fun change (0, L) p = p [ ]!
| change (n, [ ]) p = false!
| change (n, x::R) p =!
if x <= n !
then change (n-x, R) (fn A => p(x::A))
orelse !
change (n, R) p!
else change (n, R) p

REQUIRES p is total, n0, L a list of positive integers


MUST CHECK

if requirements hold for (n, L, p) and

change (n, L) p calls change (n,L) p

then requirements hold



for (n, L, p)

value equations
For all values x, n : int

L, R : int list p : int list -> bool

change (0, L) p = p [ ]!
!

change (n, [ ]) p = false!


!

change (n, x::R) p = change (n-x, R) (fn A => p(x::A))


orelse !
change (n, R) p!

if x <= n

!
change (n, x::R) p = change (n, R) p
(by the function definition)

if x > n

correctness?
Use induction on structure of L
(and the value equations)

For all positive integer lists L, all n 0,



and all total functions p : int list -> bool,
change (n, L) p = true

if there is a sublist A of L with

sum A = n and p A = true

change (n, L) p = false, otherwise

examples
change (10, [5,2,5]) (fn _ => true)

= true
change (210, [1,2,3,...,20]) (fn _ => true)

=>* true
(FAST!)
change (10, [10,5,2,5]) (fn A => length(A)>1)

= true
change (10, [10,5,2]) (fn A => length(A)>1)

= false

the right question?


Is there
a sublist of L

YES!

that ?
You
What is it?

didnt
ask

boolean blindness
By returning only a truth value we only get a

small amount of information (true or false)


From the context, we know this tells us


if it is possible to make change...

But what if we want more information?



a way to make change, if there is one
needed: a recursive function that
plan: use result type

computes a suitable sublist,

(int list) option
if there is one

mkchange
A recursive function that returns an (int list) option

mkchange : int * int list -> (int list -> bool) -> int list option!
!

REQUIRES n >= 0, L a list of positive integers, p is total !


!

ENSURES mkchange (n, L) p = SOME A,


!
where A is a sublist of L !
with sum A = n and p A = true,!
if there is such a sublist!
!

mkchange (n, L) p = NONE, otherwise

mkchange
fun mkchange (0, L) p = !
if p
! [ ] then SOME [ ] else NONE
| mkchange (n, [ ]) p =! NONE
| mkchange (n, x::R) p = !
if! x <= n !
!
then
!
case
mkchange (n-x, R) (fn A => p(x::A)) of
!SOME A => SOME (x::A)
!| NONE => mkchange (n, R) p
!
else
!
mkchange (n, R) p

mkchange
fun mkchange (0, L) p = !
if p [ ] then SOME [ ] else NONE!
| mkchange (n, [ ]) p = NONE!
| mkchange (n, x::R) p = !
if x <= n !
then !
case mkchange (n-x, R) (fn A => p(x::A)) of!
SOME A => SOME (x::A)!
| NONE => mkchange (n, R) p!
else !
mkchange (n, R) p

type check
mkchange : int * int list -> (int list -> bool) -> int list option
fun mkchange (0, L) p = !
if p [ ] then SOME [ ] else NONE!
| mkchange (n, [ ]) p = NONE!
| mkchange (n, x::R) p = !
if x <= n !
then !
case mkchange (n-x, R) (fn A => p(x::A)) of!
SOME A => SOME (x::A)!
| NONE => mkchange (n, R) p!
else !
mkchange (n, R) p

For each clause,


if argument patterns and recursive calls
have correct types, so does right-hand side

requirements check
fun mkchange (0, L) p = !
if p [ ] then SOME [ ] else NONE!
| mkchange (n, [ ]) p = NONE!
| mkchange (n, x::R) p = !
if x <= n !
then !
case mkchange (n-x, R) (fn A => p(x::A)) of!
SOME A => SOME (x::A)!
| NONE => mkchange (n, R) p!
else !
mkchange (n, R) p

REQUIRES p is total, n0, L a list of positive integers


MUST CHECK

if requirements hold for (n, L, p) and

then requirements hold

for (n, L, p)
mkchange (n, L) p calls mkchange (n,L) p

correctness?
Use induction on structure of L
For all positive integer lists L, all n 0,

and all total functions p : int list -> bool,
mkchange (n, L) p = SOME A

where A is a sublist of L with

sum A = n and p A = true,

if there is one

mkchange (n, L) p = NONE, otherwise

assessment
Weve defined
change : int * int list -> (int list -> bool) -> bool
mkchange : int * int list -> (int list -> bool) -> int list option
(and the function definitions have similar control flow)

What if the boss decides



we want yet another

type of answer
Can we stop him

once and for all?

lets go polymorphic...
Design a very flexible function

Instead of returning a bool or int list option,

assume some type a of answers (to be chosen later)


and use function parameters s and k
to implement success and failure strategy
s : int list -> a

call me, if you find a solution

k : unit -> a

call me, if you fail

more flexible type


mkchange2 : int * int list -> (int list -> bool) !
-> (int list -> 'a) -> (unit -> 'a) -> 'a

int * int list int list -> bool


mkchange2 (n, L) p s k : a
int list -> a unit -> a
We call s a success continuation

and k a failure continuation

more flexible spec


mkchange2 : int * int list -> (int list -> bool) !
-> (int list -> 'a) -> (unit -> 'a) -> 'a!
!

REQUIRES n>=0, L a list of positive integers, p total!


!

ENSURES mkchange2 (n, L) p s k = s A


!
where A is a sublist of L such that! success,

sum A = n and p A = true,!
call s
if there is one!
!

mkchange2 (n, L) p s k = k ( )
otherwise
!

fail,

call k

mkchange2
fun mkchange2 (0, L) p s k = !
if p! [ ] then s [ ] else k( )
| mkchange2 (n, [ ]) p s k =! k( )
| mkchange2 (n, x::R) p s k = !
if! x <= n !
!
then
!
mkchange2
(n-x, R)
!(fn A => p(x::A))
!(fn A => s(x::A))
!(fn ( ) => mkchange2 (n, R) p s k)
! else !
mkchange2 (n, R) p s k

mkchange2
fun mkchange2 (0, L) p s k = !
check
if p [ ] then s [ ] else k( )!
type
| mkchange2 (n, [ ]) p s k = k( )!
and
| mkchange2 (n, x::R) p s k = !
requirements
if x <= n !
then !
mkchange2 (n-x, R) !
(fn A => p(x::A))!
(fn A => s(x::A)) !
(fn ( ) => mkchange2 (n, R) p s k)!
else !
mkchange2 (n, R) p s k

correctness?
Use induction on structure of L
For all positive integer lists L, all n 0,

all total functions p : int list -> bool,

all types t and all s : int list -> t, k : unit -> t
mkchange2 (n, L) p s k = s A

where A is a sublist A L with

sum A = n and p A = true,

if there is one

mkchange2 (n, L) p s k = k( ), otherwise

utility
mkchange2 : int * int list -> (int list -> bool) !
-> (int list -> 'a) -> (unit -> 'a) -> 'a!

fun change (n, L) p = !


mkchange2 (n, L) p (fn _ => true) (fn ( ) => false)
change : int * int list -> (int list -> bool) -> bool!

fun mkchange (n, L) p = !


mkchange2 (n, L) p SOME (fn ( ) => NONE)
mkchange : int * int list -> (int list -> bool) -> (int list option)!

lessons
May want to solve a more general problem

suitable for recursion

Propagate enough information

a boolean may not be sufficient

Make sure requirements hold for

recursive calls
assuming that prior recursive calls work correctly

coming soon
Using continuation-style programs to solve
problems that require backtracking

Вам также может понравиться