Dynamic Programming via Static Incrementalization
Yanhong A. Liu and Scott D. Stoller
Abstract
Dynamic programming is an imp ortant algorithm design technique. It is used for solving
problems whose solutions involve recursively solving subproblems that share subsubproblems.
While a straightforward recursive program solves common subsubproblems rep eatedly and of-
ten takes exp onential time, a dynamic programming algorithm solves every subsubproblem just
once, saves the result, reuses it when the subsubproblem is encountered again, and takes p oly-
nomial time. This pap er describ es a systematic metho d for transforming programs written as
straightforward recursions into programs that use dynamic programming. The metho d extends
the original program to cache all p ossibly computed values, incrementalizes the extended pro-
gram with resp ect to an input increment to use and maintain all cached results, prunes out
cached results that are not used in the incremental computation, and uses the resulting in-
cremental program to form an optimized new program. Incrementalization statically exploits
semantics of b oth control structures and data structures and maintains as invariants equalities
characterizing cached results. The principle underlying incrementalization is general for achiev-
ing drastic program sp eedups. Compared with previous metho ds that p erform memoization or
tabulation, the metho d based on incrementalization is more powerful and systematic. It has
b een implemented and applied to numerous problems and succeeded on all of them.
1 Intro duction
Dynamic programming is an imp ortant technique for designing ecient algorithms [2, 44 , 13 ]. It
is used for problems whose solutions involve recursively solving subproblems that overlap. While a
straightforward recursive program solves common subproblems rep eatedly, a dynamic programming
algorithm solves every subproblem just once, saves the result in a table, and reuses the result when
the subproblem is encountered again. This can reduce the time complexity from exp onential to
p olynomial. The technique is generally applicable to all problems whose ecient solutions involve
memoizing results of subproblems [4, 5].
Given a straightforward recursion, there are two traditional ways to achieve the e ect of dynamic
programming [13 ]: memoization [32 ] and tabulation [5 ].
Memoization uses a mechanism that is separate from the original program to save the result of
each function call or reduction [32 , 18 , 21 , 33 , 23 , 41 , 43 , 37 , 17 , 1 ]. The idea is to keep a separate
table of solutions to subproblems, mo dify recursive calls to rst lo ok up in the table, and then, if
the subproblem has b een computed, use the saved result, otherwise, compute it and save the result
in the table. This metho d has two advantages. First, the original recursive program needs virtually
no change. The underlying interpretation mechanism takes care of the table lling and lo okup.
Second, only values needed by the original program are actually computed, which is optimal in
The authors gratefully acknowledge the supp ort of NSF under Grant CCR-9711253. Authors' address: Computer
Science Department, Indiana University, Lindley Hall 215, Blo omington, IN 47405. Phone: 812-855-f4373,7979g.
Email: fliu,[email protected]. 1
a sense. Memoization has two disadvantages. First, the mechanism for table lling and lo okup
has an interpretiveoverhead. Second, no general strategy for table management is ecient for all
problems.
Tabulation determines what shap e of table is needed to store the values of all p ossibly needed
sub computations, intro duces appropriate data structures for the table, and computes the table
entries in a b ottom-up fashion so that the solution to a sup erproblem is computed using available
solutions to subproblems [5 , 12 , 38 , 37 , 9, 11 , 39 , 40 , 20 , 10 ]. This overcomes b oth disadvantages of
memoization. First, table lling and lo okup are compiled into the resulting program so no separate
mechanism is needed for the execution. Second, strategies for table lling and lo okup can be
sp ecialized to be ecient for particular problems. However, tabulation has two drawbacks. First,
it generally requires a thorough understanding of the problem and a complete manual rewrite of the
program [13 ]. Second, to statically ensure that all values p ossibly needed are computed and stored,
a table that is larger than necessary is often used; it may also include solutions to subproblems not
actually needed in the original computation.
This pap er presents a p owerful metho d that statically analyzes and transforms straightforward
recursive programs to eciently cache and use the results of needed subproblems at appropriate
program p oints in appropriate data structures. The metho d has three steps: (1) extend the original
program to cache all p ossibly computed values, (2) incrementalize the extended program, with
resp ect to an input increment, to use and maintain all cached results, (3) prune out cached results
that are not used in the incremental computation, and nally use the resulting incremental program
to form an optimized program. The metho d overcomes b oth drawbacks of tabulation. First, it
consists of static program analyses and transformations that are general and systematic. Second,
it stores only values that are necessary for the optimization; it also shows exactly when and where
subproblems not in the original computation are necessarily included.
Our metho d is based on static analyses and transformations studied previously by others [49 ,
8, 46 , 6 , 34 , 19 , 47 , 39 ] and ourselves [31 , 30 , 29 , 25 , 30 ] and improves them. Yet, all three steps are
simple, automatable, and ecient and have been implemented in a prototyp e system, CACHET.
The system has b een used to optimize many programs written as straightforward recursions, includ-
ing all dynamic programming problems found in [2 , 44 , 13]. Performance measurements con rm
drastic asymptotic sp eedups.
2 Formulating the problem
Straightforward solutions to many combinatorics and optimization problems can be written as
simple recursions [44 , 13 ]. For example, the matrix-chain-multiplication problem [13 , pages 302-
314] computes the minimum number of scalar multiplications needed by any parenthesization in
multiplying a chain of n matrices, where matrix i has dimensions p p . This can b e computed
i 1 i
as m(1;n), where m(i; j ) computes the minimum numb er of scalar multiplications for multiplying
matrices i through j and can b e de ned as: for i j ,
0 if i = j
m(i; j )=
min fm(i; k )+m(k +1;j)+p p p g otherwise
i 1 j
ik j 1 k
The longest-common-subsequence problem [13 , pages 314{320] computes the length c(n; m) of the
longest common subsequence of two sequences hx ;x ; :::; x i and hy ;y ; :::; y i, where c(i; j ) can
1 2 n 1 2 m
b e de ned as: for i; j 0,
(
0 if i =0 or j =0
c(i 1;j 1)+1 if i 6= 0 and j 6= 0 and x = y
c(i; j )=
i j
max(c(i; j 1);c(i 1;j)) otherwise 2
Both of these examples are literally copied from the textb o ok by Cormen, Leiserson, and Rivest [13 ].
These recursive functions can b e written straightforwardly in the following rst-order, call-by-
value functional programming language. A program is a function f de ned by a set of mutually
0
recursive functions of the form
f (v ; :::; v ) , e
1 n
where an expression e is given by the grammar
e ::= v variable
j c(e ; :::; e ) constructor application
1 n
j p(e ; :::; e ) primitive function application
1 n
j f (e ;:::;e ) function application
1 n
j if e then e else e conditional expression
1 2 3
j let v = e in e binding expression
1 2
We include arrays as variables and use them for indexed access such as x and p ab ove. For
i j
convenience, we allow global variables to be implicit parameters to functions; such variables can
be identi ed easily for our language even if they are given as explicit parameters. Figure 1 gives
programs for the examples ab ove. Invariants ab out an input are not part of a program but are
written explicitly to b e used by the transformations. These examples do not use data constructors,
but our previous pap ers contain a numb er of examples that use them [31 , 30 , 29 ] and our metho d
handles them.
m(i; j ) where i j
, if i = j then 0
c(i; j ) where i; j 0
else msub(i; j; i)
, if i =0 _ j =0 then 0
msub(i; j; k ) where i k j 1
else if x[i]=y [j ] then c(i 1;j 1) + 1
, let s = m(i; k )+m(k +1;j)+p[i 1] p[k ] p[j ] in
else max(c(i; j 1);c(i 1;j))
if k +1 = j then s
else min(s; msub(i; j; k + 1))
Figure 1: Example programs.
These straightforward programs rep eatedly solve common subproblems and take exp onential
time. We transform them into dynamic programming algorithms that p erform ecient caching and
take p olynomial time.
We use an asymptotic cost mo del for measuring time complexity. Assuming that all primitive
functions take constant time, we need to consider only values of function applications as candidates
for caching. Caching takes extra space, which re ects the well-known trade-o between time and
space. Our primary goal is to improve the asymptotic running time of the program. Our secondary
goal is to save space bycaching only values useful for achieving the primary goal.
Caching requires appropriate data structures. In Step 1, wecache all p ossibly computed results
in a recursive tree following the structure of recursive calls. Each no de of the tree is a tuple that
bundles recursive subtrees with the return value of the current call. We use <> to denote a tuple,
and we use selectors 1st,2nd,3rd, etc. to select the rst, second, third, etc. elements of a tuple.
In Step 2, cached values are used and maintained in eciently computing function calls on
slightly incremented inputs. We use an in x op eration to denote an input increment op eration,
also called an input change (or up date) op eration. It combines a previous input x = hx ; :::; x i
1 n
0 0 0
and an increment parameter y = hy ; :::; y i to form an incremented input x = hx ; :::; x i = x y ,
1 m
n
1
0
where each x is some function of x 's and y 's. An input increment op eration we use for program
j
k
i
optimization always has a corresp onding decrement op eration pr ev such that for all x, y , and 3
0 0 0
x , if x = x y then x = pr ev (x ). Note that y need not be used. For example, an input
0 0 0 0
increment to function m in Figure 1 could be hx ;x i = hx ;x +1i or hx ;x i = hx 1;x i,
1 2 1 2
1 2 1 2
0 0 0 0
and the corresp onding decrement op erations are hx ;x i = hx ;x 1i and hx ;x i = hx +1;x i,
1 2 1 2
1 2 1 2
0
resp ectively. An input increment to a function that takes a list could b e x = cons(y; x), and the
0
corresp onding decrement op eration is x = cdr (x ).
In Step 3, cached values that are not used for an incremental computation are pruned away,
yielding functions that cache, use, and maintain only useful values.
For a function f in an original program, f denotes the function that caches all p ossibly computed
^
values of f ,andf denotes the pruned function that caches only useful values. We use x to denote
^
an un-incremented input and use r , r, and r^ to denote the return values of f (x), f (x), and f (x),
0 0
resp ectively. For any function g ,we use g to denote the incremental function that computes g (x ),
0 0 0
where x = x y , using cached results ab out x suchas g (x). So, g maytake parameter x ,aswell
as extra parameters each corresp onding to a cached result. Figure 2 summarizes the notation.
Function Return Value Denoted as Incremental Function
0
f original value r f
0
f all p ossibly computed values r f
0
^ ^
f f useful values r^
Figure 2: Notation.
3 Step 1: Caching all p ossibly computed values
Consider a function f de ned by a set of recursive functions. Program f may use global variables,
0 0
such as x and y in function c(i; j ). A possibly computed value is the value of a function call that
is computed for some but not necessarily all valuesofthe global variables. For example, function
c(i; j ) computes the value of c(i 1;j 1) only when x[i] = y [j ] but will be extended to always
compute it. Such values o ccur exactly in branches of conditional expressions whose conditions
dep end on any global variable.
We construct a program f that caches all p ossibly computed values in f . We rst apply a
0 0
simple hoisting transformation to lift function calls out of conditional expressions whose conditions
dep end on global variables. We then apply an extension transformation to cache all intermediate
results, i.e., values of all function calls, in the return value.
Hoisting transformation. Hoisting transformation H st identi es conditional expressions whose
condition dep ends on any global variable and then applies the transformation
Hst[[if e then e else e ]]= let v = e in
1 2 3 2 2
let v = e in
3 3
if e then v else v
1 2 3
For example, the hoisting transformation leaves m and msub unchanged and transforms c into
c(i; j ) , if i =0 _ j =0 then 0
else let u = c(i 1;j 1) + 1 in
1
let u =max(c(i; j 1);c(i 1;j)) in
2
if x[i]=y [j ] then u else u
1 2
H st simply lifts up the entire sub expressions in the two branches, not just the function calls
in them. Administrative simpli cation p erformed at the end of the extension transformation will 4
identify computations that o ccur at most once in subsequent computations and unwind the corre-
sp onding bindings; thus computations other than function calls will b e put down into the appropri-
ate branches then. H st is simple and ecient. The resulting program has essentially the same size
as the original program, so H st do es not increase the running time of the extension transformation
or the running times of the later incrementalization and pruning.
If we apply the hoisting transformation on arbitrary conditional expressions, the resulting pro-
gram may run slower, b ecome non-terminating, or have errors intro duced. For conditional expres-
sions whose conditions dep end on global variables, we assume that b oth branches may b e executed
to terminate correctly regardless of the condition, which holds for the large class of combinatorics
and optimization problems we handle. By limiting the hoisting transformation on these conditional
expressions, we eliminated the last two problems. The rst problem is discussed in Section 6.
Extension transformation. For each hoisted function de nition f (v ; :::; v ) , e,we construct
1 n
a function de nition
f (v ; :::; v ) , Ext[[e]] (1)
1 n
where Ext[[e]], de ned in [30 ], extends an expression e to return a nested tuple that contains the
values of all function calls made in computing e, i.e., it examines sub expressions of e in applicative
order, intro duces bindings that name the results of function calls, builds up tuples of these values
together with the values of the original sub expressions, and passes these values from sub computa-
tions to enclosing computations. The rst comp onentof a tuple corresp onds to an original return
value. Next, administrative simpli cations clean up the resulting program. This yields a program
f that embeds values of all p ossibly computed function calls in its return value. For the hoisted
0
programs m and c, the extension transformation pro duces the following functions:
m(i; j ) , if i = j then < 0 >
msub(i; j; i) else
msub(i; j; k ) , let v = m(i; k ) in
1
m(k +1;j) in let v =
2
let s =1st(v )+1st(v )+p[i 1] p[k ] p[j ] in
1 2
if k +1 = j then
1 2
else let v = msub(i; j; k +1) in
< min(s; 1st(v ));v ;v ;v >
1 2
c(i; j ) , if i =0 _ j =0 then < 0 >
else let v =c (i 1;j 1) in
1
let v =c (i; j 1) in
2
let v =c (i 1;j) in
3
if x[i]=y [j ] then < 1st(v )+1;v ;v ;v >
1 1 2 3
else < max(1st(v ); 1st(v ));v ;v ;v >
2 3 1 2 3
4 Step 2: Static incrementalization
The essence of our metho d is to use and maintain cached values eciently as a computation
pro ceeds, i.e., we incrementalize f with resp ect to an input increment op eration . Precisely,we
0
transform f (x y ) to use the cached value of f (x) rather than compute from scratch.
0 0
An input increment operation corresp onds to a minimal up date to the input parameters. We
rst describ e a general metho d for identifying . We then give a powerful metho d, called static
0
incrementalization, that constructs an incremental version f for each function f in the extended
program and allows an incremental function to have multiple parameters that represent cached
values. 5
Input increment op eration. An input increment should re ect how a computation pro ceeds. In
general, a function mayhavemultiple ways of pro ceeding dep ending on the particular computations
involved. There is no general metho d for identifying all of them or the most appropriate ones. Here
we prop ose a metho d that can systematically identify a general class of them. The idea is to use a
minimal input change that is in the opposite direction of change compared to arguments of recursive
calls. Using the opp osite direction of change yields an increment; using a minimal change allows
maximum reuse, i.e., maximum incrementality.
Consider a recursively de ned function f . Formulas for the p ossible arguments of recursive calls
0
to f in computing f (x)can b e determined statically. For example, for function c(i; j ), recursive
0 0
calls to c have the set of p ossible arguments S = fhi 1;j 1i; hi; j 1i; hi 1;jig, and for function
c
m(i; j ), recursive calls to m have the set of p ossible arguments S = fhi; k i; hk +1;jiji k j 1g.
m
The latter is simpli ed from S = fha; ci; hc +1;bija c b 1;a = i; b = j g where a; b; c are fresh
m
variables that corresp ond to i; j; k in msub; the equalities are based on arguments of the recursive
calls involved (in this case msub); and the inequalities are obtained from the inequalities on these
arguments. The simpli cation here, as well as the manipulations b elow, can b e done automatically
using Omega [42 ].
Represent the arguments of recursive calls so that the di erences between them and x are
explicit. For function c, S is already in this form, and for function m, S is rewritten as fhi; j
c m
l i; hi + l; j ij1 l j ig. Then, extract minimal di erences that cover all of these recursive
calls. The partial ordering on di erences is: a di erence involving fewer parameters is smaller; a
di erence in one parameter with smaller magnitude is smaller; other di erences are incomparable.
A set of di erences covers a recursive call if the argument to the call can b e obtained by rep eated
application of the given di erences. So, we rst compute the set of minimal di erences and then
remove from it each element that is covered bythe remaining elements. For function c, we obtain
fhi; j 1i; hi 1;jig, and for function m, we obtain fhi; j 1i; hi +1;jig. Elements of this set
represent decrement op erations. Finally, take the opp osite of each decrement op eration to obtain
an increment op eration ,intro ducing a parameter y if needed (e.g., for increments that use data
constructions). For function c, we obtain hi; j +1i and hi +1;ji, and for function m, we obtain
hi; j +1i and hi 1;ji. Even though nding an input increment op eration is theoretically hard in
general, it is usually straightforward.
Typically, a function involves rep eatedly solving common subproblems when it contains multiple
recursive calls to itself. If there are multiple input increment op erations, then any one may be
used to incrementalize the program and nally form an optimized program; the rest may b e used
to further incrementalize the resulting optimized program, if it still involves rep eatedly solving
common subproblems. For example, for program c, either hi; j +1i or hi +1;ji will lead to a nal
optimized program, and for program m,bothhi 1;ji and hi; j +1i need to b e used, and they may
b e used in either order.
Static incrementalization. Given a program f and an input increment op eration , incremen-
0
0 0
talization symb olically transforms f (x )forx = x y to replace sub computations with retrievals
0
of their values from the value r of f (x). This exploits equality reasoning, based on control and
0
0
data structures of the program and prop erties of primitive op erations. The resulting program f
0
usesr or parts ofr as additional arguments, called cache arguments, and satis es: if f (x)=r and
0
0 0 0 1 0 0
(x ; r)=r . f (x )=r , then f
0 0
The idea is to establish the strongest invariants, esp ecially those ab out cache arguments, at
all calls and maximize their usage. At the end, unused candidate cache arguments are eliminated.
0 0 0 0 1
(x; y ; r)=r . slightly di erently: if f (x)=r and f (x y )=r ,thenf In previous pap ers, we de ned f
0 0 0 0 6
Reducing running time corresp onds to maximizing uses of invariants; reducing space corresp onds
to maintaining weakest invariants for all uses. It is imp ortant that the metho ds for establishing and
using invariants are sp ecialized so that they are automatable. The precise algorithm is describ ed
b elow. Its use is illustrated afterwards using the running examples.
0 0
The algorithm starts with transforming f (x ) for x = x y and f (x)=r and rst uses the
0 0
decrement op eration to establish an invariant ab out function arguments. More precisely, it starts
0 0
with transforming f (x ) with invariant f (pr ev (x )) =r , wherer is a candidate cache argument. It
0 0
0
may use other invariants ab out x if given. Invariants given or formed from the enclosing conditions
and bindings are called context. The algorithm transforms function applications recursively. There
0 0
are four cases at a function application f (e ; :::; e ).
n
1
0 0
(1) If f (e ; :::; e ) sp ecializes, by de nition of f , under its context to a base case, i.e., an expression
1 n
with no recursive calls, then replace it with the sp ecialized expression.
0 0
(2) Otherwise, if f (e ; :::; e ) equals a retrieval from a cache argument based on an invariant
1 n
ab out the cache argument in its context, then replace it with the retrieval.
0 0 0
(3) Otherwise, if an incremental version f of f has been intro duced, then replace f (e ; :::; e )
n
1
0
with a call to f if the corresp onding invariants can b e maintained; if some invariants can not
0
b e maintained, then eliminate them and retransform from where f was intro duced.
0 0 0 0
(4) Otherwise, intro duce an incremental version f of f and replace f (e ; :::; e )withacalltof ,
n
1
as describ ed b elow.
In general, the replacement in case (1) is also done, rep eatedly, if the sp ecialized expression con-
tains only recursive calls whose arguments are closer to, and will equal after a b ounded number of
such replacements, arguments for base cases or arguments on which retrievals can be done. Since
a b ounded number of invariants are used at a function application, as describ ed b elow, the re-
transformation in case (3) can only b e done a b ounded numb er of times. So, the algorithm always
terminates.
0 0 0
), let Inv be the set of invariants To intro duce an incremental version f of f at f (e ; :::; e
n
1
0 0
ab out cache arguments or context information at f (e ; :::; e ). Those ab out cache arguments are
n
1
of the form g (e ; :::; e ) = e , where e is either a candidate cache argument in the enclosing
i i1 in ir ir
i
environment or a selector applied to such an argument. Those ab out context information are of
the form e = tr ue, e = false, or v = e, obtained from conditions or bindings. For simplicity,we
0
assume that all bound variables are renamed so that they are distinct. Intro duce f to compute
00 00 00 0 00 0 00 00
f (x ; :::; x ) for x = e ; :::; x = e , where x ; :::; x are fresh variables, and deduce invariants
1 n 1 1 n n 1 n
00 00 0 00 0 00
ab out x ; :::; x based on Inv. The deduction uses equations e = x ; :::; e = x to eliminate
n n n
1 1 1
variables in Inv and can be done automatically using Omega [42 ]. Resulting equations relating
00 00
; :::; x are used also to duplicate other invariants deduced. If a resulting invariant still uses x
n
1
00 00
a variable other than x ; :::; x , discard it. Finally, for each invariant ab out a cache argument,
n
1
replace its right hand side with a fresh variable, which b ecomes a candidate cache argument of
0 0
f . This yields the set of invariants now asso ciated with f . Note that invariants ab out cache
00 00 00 00 00 00
arguments have the form g (e ; :::; e ) = r , where e ; :::; e use only variables x ; :::; x , and
i i
n
1
i1 in i1 in
i i
r is a fresh variable. Among the left hand sides of these invariants, identify an application of f
i
00 00
whose arguments have a minimum di erence from x ; :::; x ; if such an application exists, denote
1 n
00 00
it f (e ; :::; e ).
1 n
0 00 00 00 00
To obtain a de nition of f , unfold f (x ; :::; x ) and then exploit conditionals in f (x ; :::; x )and
1 n 1 n
00 00 0
f (e ; :::; e ) (if it exists) and comp onents in the candidate cache arguments of f . To exploit condi-
n
1
00 00 00 00
tionals in f (x ; :::; x ), move function applications inside branches of the conditionals in f (x ; :::; x )
n n
1 1 7
whenever p ossible, preserving control dep endencies incurred by the order of conditional tests and
data dep endencies incurred by the bindings. This is done by rep eatedly applying the following trans-
formation in applicative order to the unfolded expression. For any t(e ; :::; e ) b eing c(e ; :::; e ),
1 1
k k
p(e ; :::; e ), f (e ; :::; e ), if e then e else e ,orlet v = e in e ,ife is if e then e else e ,
1 1 1 2 3 1 2 i i1 i2 i3
k k
where i 6=2; 3ift is a conditional, and i 6=2 ore do es not dep end on v if t is a binding expression,
i1
then transform t(e ; :::; e )toif e then t(e ; :::; e ;e ;e ; :::; e ) else t(e ; :::; e ;e ;e ; :::; e ).
1 i1 1 i 1 i2 i+1 1 i 1 i3 i+1
k k k
This transformation preserves the semantics. It may increase the co de size, but it do es not increase
00 00
the running time of the resulting program. To exploit the conditionals in f (e ; :::; e ), intro duce
n
1
00 00
conditions from f (e ; :::; e ) in the transformed expression just obtained and put function appli-
n
1
cations inside b oth branches that followsuch a condition. This is done by applying the following
transformation in outermost- rst order to the conditionals in the transformed expression just ob-
tained. For each branch e of the conditional that contains a function application, let e be the
i
00 00
outermost condition in f (e ; :::; e ) that is not implied by the context of e ; if e uses only vari-
i
1 n
ables de ned in the context of e and takes constant time to compute, and the two branches in
i
00 00
f (e ; :::; e ) that dep end on e contain di erent function applications in some comp onent, then
1 n
transform e to if e then e else e . To exploit each comp onent in a candidate cache argument
i i i
00 00
r where there is an invariant g (e ; :::; e ) = r , for each branch in the transformed expression,
i i i
i1 in
i
00 00
) under the context of that branch. This may yield additional function ap- sp ecialize g (e ; :::; e
i
i1 in
i
plications that equal various comp onents of r . After these control structures and data structures
i
0 0
are exploited, we simplify primitive op erations on x ; :::; x and transform function applications
n
1
0
recursively based on the four cases describ ed. Finally, after we obtain a de nition of f , replace the
0 0 0 0 0
function application f (e ; :::; e ) with a call to f with arguments e ; :::; e and cache arguments
n n
1 1
e 's for the invariants used.
ir
The simpli cations and equality reasoning needed for all the problems we have encountered
involve only recursive data structures and Presburger arithmetic and can b e fully automated.
0 0
Longest common subsequence. Incrementalize c under hi ;j i = hi +1;ji. We start with
0 0 0 0 0 0
c(i ;j ), with cache argument r and invariant c(pr ev (i ;j )) = c(i 1;j ) = r; the invariants
0 0
i ;j > 0may also b e included but do not a ect any transformation b elow, so they are omitted for
0 0 0
convenience. This is case (4), so weintro duce incremental versionc to computec (i ;j ). Unfolding
the de nition ofc and listing conditions to b e exploited, we obtain the co de b elow. The false branch
0 0 0 0
ofc (i ;j ) is duplicated with the additional condition i 1=0 _ j = 0, which is copied from the
0 0
condition in de nition of c(i 1;j ); for convenience, three function applications b ounded to v
1
0 0
to v are not put inside branches that follow condition x[i ]=y [j ], since their transformations are
3
not a ected, and simpli cation at the end can take them backout.
0 0 0 0
c(i ;j )= if i =0 _ j =0 then < 0 >
0 0
else if i 1=0 _ j =0 then
0 0
let v =c(i 1;j 1) in
1
0 0
let v =c(i ;j 1) in
2
0 0
let v =c(i 1;j ) in
3
0 0
if x[i ]= y [j ] then < 1st(v )+1;v ;v ;v >
1 1 2 3
else < max(1st(v ); 1st(v ));v ;v ;v >
2 3 1 2 3
0 0
else let v =c(i 1;j 1) in
1
0 0
let v =c(i ;j 1) in
2
0 0
let v =c(i 1;j ) in
3
0 0
if x[i ]=y [j ] then < 1st(v )+1;v ;v ;v >
1 1 2 3
else < max(1st(v ); 1st(v ));v ;v ;v >
2 3 1 2 3
0 0
In the second branch, i 1 = 0 is true, since j =0 would imply that the rst branchistaken.
The rst and third calls fall in case (1) and sp ecialize to < 0 >. The second call falls in case (3) 8
0 0 0
and equals a recursive call toc with arguments i ;j 1 and cache argument < 0 > since wehavea
0 0
corresp onding invariant c(i 1;j 1) = < 0 >. Additional simpli cation unwindsbindingsfor v
1
and v , simpli es 1st(< 0 >) + 1 to 1, and simpli es max (1st(v ); 1st(< 0 >)) to 1st(v ).
3 2 2
0 0 0 0
In the third branch, condition i 1=0_ j = 0 is false;c (i 1;j )by de nition ofc equals its
0 0 0 0 0 0
second branch wherec (i 1;j 1) is b ound to v ,andthusc (i 1;j )=r impliesc (i 1;j 1) =
2
3rd(r ). The rst call falls in case (2) and equals 3rd(r ). The second call falls in case (3) and
0 0 0
equals a recursive call to c with arguments i ;j 1 and cache argument 3rd(r )) since we have a
0 0
corresp onding invariant c(i 1;j 1) = 3rd(r ). The third call falls in case (2) and equals r. We
obtain
0 0 0 0 0
c (i ;j ; r) , if i =0 _ j =0 then < 0 >
0
else if i 1=0 then
0 0 0
let v =c (i ;j 1;< 0 >) in
2
0 0
if x[i ]=y [j ] then < 1;< 0 >; v ;< 0 >>
2
else < 1st(v );< 0 >; v ;< 0 >>
2 2
else let v =3rd(r ) in
1
0 0 0
let v =c (i ;j 1; 3rd(r )) in
2
let v =r in
3
0 0
if x[i ]=y [j ] then < 1st(v )+1;v ;v ;v >
1 1 2 3
else < max(1st(v ); 1st(v ));v ;v ;v >
2 3 1 2 3
0 0 0 0 0 0 0 0 0
Ifr =c(i 1;j ), thenc (i ;j ; r)=c (i ;j ), andc takes time and space linear in j , for caching
and maintaining a linear list.
0 0
Matrix-chain multiplication. Incrementalize m under hi ;j i = hi; j +1i. We start with
0 0 0 0 0 0
m(i ;j ), with cache argument r and invariants m(i ;j 1) = r and i j . This is case (4),
0 0 0
so we intro duce incremental version m to compute m(i ;j ). Unfolding m, listing conditions, and
sp ecializing the second branch, we obtain the co de b elow.
0 0 0 0
m(i ;j )= if i = j then < 0 >
0 0 0 0 0
else if i = j 1 then