Introduction to Programming in ATS: | ||
---|---|---|
<<< Previous | Datatype Refinement | Next >>> |
The data structure I implement in this section is based on one presented in the book titled Pure Funtional Data Structures by Chris Okasaki, where more elaboration on data structures of this kind can be found.
Let us first declare a datatype as follows:
typedef pt (a:t@ype) = '(a, a) datatype ralist (a:t@ype+, n:int) = | RAnil (a, 0) | {n:pos} RAevn (a, 2*n) of ralist (pt a, n) | {n:nat} RAodd (a, 2*n+1) of (a, ralist (pt a, n)) // end of [ralist] |
The constructor RAnil is for constructing a value representing the empty list.
In order to construct a value representing a list of 2*I elements for some I > 0, we first construct a value representing a list of I pairs of adjacent elements in the (original) list and then apply the constructor RAevn to the value.
In order to construct a value representing a list of 2*I+1 elements for some I >= 0, we take out the head of the list and construct a value representing a list of I pairs of adjacent elements in the tail of the (original) list and then apply the constructor RAodd to the head element and the value.
Note that the datatype ralist is not supported in ML even if the index representing list length is erased. This kind of datatypes are often referred to as nested datatypes, which are also supported in Haskell.
The following defined function ralist_length computes the length of a random-access list:
fun{a:t@ype} ralist_length // O(log n) {n:nat} .<n>. (xs: ralist (a, n)): int n = case+ xs of | RAnil () => 0 | RAevn xxs => 2 * ralist_length<pt(a)> (xxs) | RAodd (_, xxs) => 2 * ralist_length<pt(a)> (xxs) + 1 // end of [ralist_length] |
Consing means to form a new list with an element and a list such that the element and the list are the head and tail of the new list. The following defined function ralist_cons implements consing for random-access lists:
fun{a:t@ype} ralist_cons // O(1), amortized {n:nat} .<n>. (x0: a, xs: ralist (a, n)): ralist (a, n+1) = case+ xs of | RAnil () => RAodd (x0, RAnil) | RAevn (xxs) => RAodd (x0, xxs) | RAodd (x1, xxs) => RAevn (ralist_cons<pt(a)> ( '(x0, x1), xxs )) // end of [RAodd] // end of [ralist_cons] |
Unconsing does the opposite of consing: It returns a pair consisting of the head and tail of a given non-empty list. The following defined function ralist_uncons implements unconsing for random-access lists:
fun{a:t@ype} ralist_uncons // O(1), amortized {n:pos} .<n>. (xs: ralist (a, n)): (a, ralist (a, n-1)) = case+ xs of | RAevn (xxs) => let val (xx, xxs) = ralist_uncons<pt(a)> (xxs) in (xx.0, RAodd (xx.1, xxs)) end // end of [RAevn] | RAodd (x, xxs) => (case+ xxs of (* // Note: [=>>] is needed for enforcing sequentiality // during typechecking: *) RAnil () => (x, RAnil) | _ =>> (x, RAevn (xxs)) ) // end of [RAodd] // end of [ralist_uncons] |
Given a random-access list xs of length n, the elements in it are numbered from 0 to n-1, inclusively. We can find element i in the list xs, where i is assumed to be a natural number less than n, by implementing the following algorithm:
Assume the length n is even. Then xs is of the form RAevn(xxs), where xxs is a list of pairs. Let i2 be i/2 and we find element i2 in xxs, which is a pair. Let xx refer to this pair. If i is even, then the left component of xx is element i in xs. Otherwise, the right component is.
Assume the length n is odd. Then xs is of the form RAodd(x, xxs), where xxs is a list of pairs. If i equals 0, the x is element i in xs. Otherwise, let i1 be i-1 and i2 be i1/2 and we find element i2 in xxs, which is a pair. Let xx refer to this pair. If i1 is even, then the left component of xx is element i in xs. Otherwise, the right component is.
fun{a:t@ype} ralist_lookup // O(log(n))-time {n:int} {i:nat | i < n} .<n>. (xs: ralist (a, n), i: int i): a = case+ xs of | RAevn xxs => let val i2 = i / 2 val lr = i - 2 * i2 val xx = ralist_lookup<pt(a)> (xxs, i2) in if lr = 0 then xx.0 else xx.1 end // end of [RAevn] | RAodd (x, xxs) => if i > 0 then let val i1 = i - 1 val i2 = i1 / 2 val lr = i1 - 2 * i2 val xx = ralist_lookup<pt(a)> (xxs, i2) in if lr = 0 then xx.0 else xx.1 end else x // end of [RAodd] // end of [ralist_lookup] |
Given a random-access list xs of length n, an index that is a natural number less than n and an element x0, the following defined function ralist_update returns another random-access that is the same as xs except element i in it being x0:
fun{a:t@ype} ralist_update // O(log(n))-time {n:int} {i:nat | i < n} .<n>. ( xs: ralist (a, n), i: int i, x0: a ) : ralist (a, n) = let // fun{a:t@ype} fupdate {n:int} {i:nat | i < n} .<n,1>. ( xs: ralist (a, n), i: int i, f: a -<cloref1> a ) : ralist (a, n) = case+ xs of | RAevn xxs => RAevn (fupdate2 (xxs, i, f)) | RAodd (x, xxs) => if i > 0 then RAodd (x, fupdate2 (xxs, i-1, f)) else RAodd (f(x), xxs) (* end of [fupdate] *) // and fupdate2 {n2:int} {i:nat | i < n2+n2} .<2*n2,0>. ( xxs: ralist (pt(a), n2), i: int i, f: a -<cloref1> a ) : ralist (pt(a), n2) = let val i2 = i / 2 val lr = i - 2 * i2 val f2 = ( if lr = 0 then lam xx => '(f(xx.0), xx.1) else lam xx => '(xx.0, f(xx.1)) ) : pt(a) -<cloref1> pt(a) in fupdate<pt(a)> (xxs, i2, f2) end // end of [fupdate2] // in fupdate (xs, i, lam _ => x0) end // end of [ralist_update] |
The code employed for illustration in this section plus some additional code for testing is available on-line.
<<< Previous | Home | Next >>> |
Sequentiality of Pattern Matching | Up | Example: Functional Red-Black Trees |