Designing Algorithms

At first glance, the algorithms <#66238#><#33601#>move-until-out<#33601#><#66238#> and <#66239#><#33602#>quick-sort<#33602#><#66239#> have little in common. One processes structures, the other one lists. One creates a new structure for the generative step, the other one splits up a list into three pieces and recurs on two of them. In short, a comparison of the two examples of generative recursion suggests that the design of algorithms is an <#33603#>ad hoc<#33603#> activity and that it is impossible to come up with a general design recipe. A close look, however, suggests a different picture. First, even though we speak of ``algorithms as processes that solve problems'' they are still functions that consume and produce data. In other words, we still choose data to represent a problem, and we must definitely understand the nature of our data if we wish to understand the process. Second, we describe the processes in terms of data, for example, ``creating a new structure'' or ``partitioning a list of numbers.'' Third, we always distinguish between input data for which it is trivial to produce a solution and those for which it is not. Fourth, the generation of problems is the key to the design of algorithms. Although the idea of how to generate a new problem might be independent of a data representation, it must certainly be implemented for whatever form of representation we choose for our problem. Finally, once the generated problems have been solved, the solutions must be combined with other values. Let us examine the six general stages of our structural design recipe in light of our discussion:
Data analysis & design:
The choice of a data representation for a problem often affects our thinking about the process. Sometimes the description of a process dictates a particular choice of representation. On other occasions, it is possible and worthwhile to explore alternatives. In any case, we must analyze and define our data collections.
Contract, Purpose, Header:
We also need a contract, a definition header, and a purpose statement. Since the generative step has no connection to the structure of the data definition, the purpose statement should not just specify <#33605#>what<#33605#> the function does but should also include a comment that explains in general terms <#33606#>how<#33606#> the it works.
Function examples:
In our previous design recipes, the function examples merely specified which output the function should produce for some given input. For algorithms, examples should illustrate <#33607#>how<#33607#> the algorithm proceeds for some given input. This helps us design, and readers understand, the algorithm. For functions such as <#66240#><#33608#>move-until-out<#33608#><#66240#> the process is trivial and doesn't need more than a few words. For others, including, <#66241#><#33609#>quick-sort<#33609#><#66241#>, the process relies on a non-trivial idea for its generative step, and its explanation requires a good example such as the one in figure~#figsortill#33610>.
Template:
Our discussion suggests a general template for algorithms:
<#33615#>(d<#33615#><#33616#>efine<#33616#> <#33617#>(generative-recursive-fun<#33617#> <#33618#>problem)<#33618#>
  <#33619#>(c<#33619#><#33620#>ond<#33620#> 
    <#33621#>[<#33621#><#33622#>(trivially-solvable?<#33622#> <#33623#>problem)<#33623#> 
     <#33624#>(determine-solution<#33624#> <#33625#>problem)]<#33625#> 
    <#33626#>[<#33626#><#33627#>else<#33627#> 
     <#33628#>(c<#33628#><#33629#>ombine-solutions<#33629#> 
       <#33630#>...<#33630#> <#33631#>problem<#33631#> <#33632#>...<#33632#> 
       <#33633#>(generative-recursive-fun<#33633#> <#33634#>(generate-problem-1<#33634#> <#33635#>problem))<#33635#> 
       <#33636#>#tex2html_wrap_inline73614#<#33636#> 
       <#33637#>(generative-recursive-fun<#33637#> <#33638#>(generate-problem-n<#33638#> <#33639#>problem)))]<#33639#><#33640#>))<#33640#> 
Definition:
Of course, this template is only a suggestive blueprint, not a definitive shape. Each function in the template is to remind us that we need to think about the following four questions:
  1. What is a trivially solvable problem?
  2. What is a corresponding solution?
  3. How do we generate new problems that are more easily solvable than the original problem? Is there one new problem that we generate or are there several?
  4. Is the solution of the given problem the same as the solution of (one of) the new problems? Or, do we need to combine the solutions to create a solution for the original problem? And, if so, do we need anything from the original problem data?
To define the algorithm, we must express the answers to these four questions in terms of our chosen data representation.
Test:
Once we have a complete function, we must also test it. As before, the goal of testing is to discover bugs and to eliminate them. Remember that testing cannot validate that the function works correctly for all possible inputs. Also remember that it is best to formulate tests as boolean-valued expressions that automatically compare the expected value with the computed value (see section~#secequaltest#33646>).

<#33650#>Exercise 26.0.1<#33650#> Formulate informal answers to the four key questions for the problem of modeling a ball's movement across a canvas until it is out of bounds. ~ external Solution<#66242#><#66242#> <#33657#>Exercise 26.0.2<#33657#> Formulate informal answers to the four key questions for the <#66243#><#33659#>quick-sort<#33659#><#66243#> problem. How many different instances of <#66244#><#33660#>generate-problem<#33660#><#66244#> are needed?~ external Solution<#66245#><#66245#>