Making Choices

A user cannot distinguish <#66317#><#34032#>sort<#34032#><#66317#> and <#66318#><#34033#>quick-sort<#34033#><#66318#>. Both consume a list of numbers; both produce a list that consists of the same numbers but is arranged in ascending order. To an observer, the functions are completely equivalent. This raises the question which of the two a programmer should provide. More generally, if we can develop a function using structural recursion and an equivalent one using generative recursion, what should we do? To understand this choice better, let's discuss another classical example of generative recursion from mathematics: the problem of finding the greatest common divisor of two positive natural numbers. All such numbers have at least one divisor in common: 1. On occasion, this is also the only common divisor. For example, 2 and 3 have only 1 as common divisor because 2 and 3, respectively, are the only other divisors. Then again, 6 and 25 are both numbers with several divisors:
  1. 6 is evenly divisible by 1, 2, 3, and 6;
  2. 25 is evenly divisible by 1, 5, and 25.
Still, the greatest common divisior of 25 and 6 is 1. In contrast, 18 and 24 have many common divisors:
  1. 18 is evenly divisible by 1, 2, 3, 6, and 9;
  2. 24 is evenly divisible by 1, 2, 3, 4, 6, 8, and 12.
The greatest common divisor is 6. Following the design recipe, we start with a contract and a purpose statement:
<#71473#>;; <#66319#><#34044#>gcd<#34044#> <#34045#>:<#34045#> <#34046#>N<#34046#><#34047#>[<#34047#><#34048#>;SPMgt;=<#34048#> <#34049#>1]<#34049#> <#34050#>N<#34050#><#34051#>[<#34051#><#34052#>;SPMgt;=<#34052#> <#34053#>1]<#34053#> <#34054#><#34054#><#34055#>-;SPMgt;<#34055#><#34056#><#34056#> <#34057#>N<#34057#><#66319#><#71473#>
<#71474#>;; to find the greatest common divisior of <#66320#><#34058#>n<#34058#><#66320#> and <#66321#><#34059#>m<#34059#><#66321#><#71474#> 
<#34060#>(define<#34060#> <#34061#>(gcd<#34061#> <#34062#>n<#34062#> <#34063#>m)<#34063#> <#34064#>...)<#34064#> 
The contract specifies the precise inputs: natural numbers that are greater or equal to <#66322#><#34068#>1<#34068#><#66322#> (not <#66323#><#34069#>0<#34069#><#66323#>). Now we need to make a decision whether we want to pursue a design based on structural or on generative recursion. Since the answer is by no means obvious, we develop both. For the structural version, we must consider which input the function should process: <#66324#><#34070#>n<#34070#><#66324#>, <#66325#><#34071#>m<#34071#><#66325#>, or both. A monent's consideration suggests that what we really need is a function that starts with the smaller of the two and outputs the first number smaller or equal to this input that evenly divides both <#66326#><#34072#>n<#34072#><#66326#> and <#66327#><#34073#>m<#34073#><#66327#>. We use <#66328#><#34074#>local<#34074#><#66328#> to define an appropriate auxiliary function:
<#71475#>;; <#66329#><#34079#>gcd-structural<#34079#> <#34080#>:<#34080#> <#34081#>N<#34081#><#34082#>[<#34082#><#34083#>;SPMgt;=<#34083#> <#34084#>1]<#34084#> <#34085#>N<#34085#><#34086#>[<#34086#><#34087#>;SPMgt;=<#34087#> <#34088#>1]<#34088#> <#34089#><#34089#><#34090#>-;SPMgt;<#34090#><#34091#><#34091#> <#34092#>N<#34092#><#66329#><#71475#>
<#71476#>;; to find the greatest common divisior of <#66330#><#34093#>n<#34093#><#66330#> and <#66331#><#34094#>m<#34094#><#66331#><#71476#> 
<#71477#>;; <#34095#>structural<#34095#> recursion using data definition of <#66332#><#34096#>N<#34096#><#34097#>[<#34097#><#34098#>;SPMgt;=<#34098#> <#34099#>1]<#34099#><#66332#><#71477#> 
<#34100#>(d<#34100#><#34101#>efine<#34101#> <#34102#>(gcd-structural<#34102#> <#34103#>n<#34103#> <#34104#>m)<#34104#> 
  <#34105#>(l<#34105#><#34106#>ocal<#34106#> <#34107#>((d<#34107#><#34108#>efine<#34108#> <#34109#>(first-divisior-;SPMlt;=<#34109#> <#34110#>i)<#34110#> 
            <#34111#>(c<#34111#><#34112#>ond<#34112#> 
              <#34113#>[<#34113#><#34114#>(=<#34114#> <#34115#>i<#34115#> <#34116#>1)<#34116#> <#34117#>1]<#34117#> 
              <#34118#>[<#34118#><#34119#>else<#34119#> <#34120#>(c<#34120#><#34121#>ond<#34121#> 
                      <#34122#>[<#34122#><#34123#>(and<#34123#> <#34124#>(=<#34124#> <#34125#>(remainder<#34125#> <#34126#>n<#34126#> <#34127#>i)<#34127#> <#34128#>0)<#34128#> 
                            <#34129#>(=<#34129#> <#34130#>(remainder<#34130#> <#34131#>m<#34131#> <#34132#>i)<#34132#> <#34133#>0))<#34133#> 
                       <#34134#>i]<#34134#> 
                      <#34135#>[<#34135#><#34136#>else<#34136#> <#34137#>(first-divisior-;SPMlt;=<#34137#> <#34138#>(-<#34138#> <#34139#>i<#34139#> <#34140#>1))]<#34140#><#34141#>)]<#34141#><#34142#>)))<#34142#> 
    <#34143#>(first-divisior-;SPMlt;=<#34143#> <#34144#>(min<#34144#> <#34145#>m<#34145#> <#34146#>n))))<#34146#> 
The conditions ``evenly divisible'' have been encoded as <#66333#><#34150#>(=<#34150#><#34151#> <#34151#><#34152#>(remainder<#34152#>\ <#34153#>n<#34153#>\ <#34154#>i)<#34154#>\ <#34155#>0)<#34155#><#66333#> and <#66334#><#34156#>(=<#34156#>\ <#34157#>(remainder<#34157#>\ <#34158#>m<#34158#>\ <#34159#>i)<#34159#>\ <#34160#>0)<#34160#><#66334#>. The two ensure that <#66335#><#34161#>i<#34161#><#66335#> divides <#66336#><#34162#>n<#34162#><#66336#> and <#66337#><#34163#>m<#34163#><#66337#> without a remainder. Testing <#66338#><#34164#>gcd-structural<#34164#><#66338#> with the examples shows that it finds the expected answers. Although the design of <#66339#><#34165#>gcd-structural<#34165#><#66339#> is rather straightforward, it is also naive. It simply tests every number whether it divides both <#66340#><#34166#>n<#34166#><#66340#> and <#66341#><#34167#>m<#34167#><#66341#> evenly and returns the first such number. For small natural numbers, this process works just fine. Consider the following example, however:
<#34172#>(gcd-structural<#34172#> <#34173#>101135853<#34173#> <#34174#>45014640)<#34174#>
The result is <#66342#><#34178#>177<#34178#><#66342#> and to get there <#66343#><#34179#>gcd-structural<#34179#><#66343#> had to compare 101135676, that is, 101135853 - 177, numbers. This is a large effort and even reasonably fast computers spend several minutes on this task.
<#34182#>Exercise 26.3.1<#34182#> Enter the definition of <#66344#><#34184#>gcd-structural<#34184#><#66344#> into the <#34185#>Definitions<#34185#> window and evaluate <#66345#><#34186#>(time<#34186#>\ <#34187#>(gcd-structural<#34187#><#34188#> <#34188#><#34189#>101135853<#34189#>\ <#34190#>45014640))<#34190#><#66345#> in the <#34191#>Interactions<#34191#> window. <#34192#>Hint:<#34192#> Once <#66346#><#34193#>gcd-structural<#34193#><#66346#> is tested, switch to the MzScheme language (without debugging). It runs programs faster than the lower language levels but offers less protection. Add
<#34198#>(require-library<#34198#> <#34199#>``macro.ss'')<#34199#>
to the top of the <#34203#>Definitions<#34203#> window. Have some reading handy!~ external Solution<#66347#><#66347#>
Since mathematicians recognized the inefficiency of the ``structural algorithm'' a long time ago, they studied the problem of finding divisiors in more depth. The essential insight is that for two natural numbers <#66348#><#34211#>larger<#34211#><#66348#> and <#66349#><#34212#>smaller<#34212#><#66349#>, their greatest common divisor is equal to the greatest common divisior of <#66350#><#34213#>smaller<#34213#><#66350#> and the <#66351#><#34214#>remainder<#34214#><#66351#> of <#66352#><#34215#>larger<#34215#><#66352#> divided into <#66353#><#34216#>smaller<#34216#><#66353#>. Here is how we can put this insight into equational form:
  <#34221#>(gcd<#34221#> <#34222#>larger<#34222#> <#34223#>smaller)<#34223#> 
<#34224#>=<#34224#> <#34225#>(gcd<#34225#> <#34226#>smaller<#34226#> <#34227#>(remainder<#34227#> <#34228#>larger<#34228#> <#34229#>smaller))<#34229#> 
Since <#66354#><#34233#>(remainder<#34233#>\ <#34234#>larger<#34234#>\ <#34235#>smaller)<#34235#><#66354#> is smaller than both <#66355#><#34236#>larger<#34236#><#66355#> and <#66356#><#34237#>smaller<#34237#><#66356#>, the right-hand side use of <#66357#><#34238#>gcd<#34238#><#66357#> consumes <#66358#><#34239#>smaller<#34239#><#66358#> first. Here is how this insight applies to our small example:
  1. The given numbers are <#66359#><#34241#>18<#34241#><#66359#> and <#66360#><#34242#>24<#34242#><#66360#>.
  2. According to the mathematicians' insight, they have the same greatest common divisor as <#66361#><#34243#>18<#34243#><#66361#> and <#66362#><#34244#>6<#34244#><#66362#>.
  3. And these two have the same greatest common divisor as <#66363#><#34245#>6<#34245#><#66363#> and <#66364#><#34246#>0<#34246#><#66364#>.
And here we seem stuck because <#66365#><#34248#>0<#34248#><#66365#> is nothing expected. But, <#66366#><#34249#>0<#34249#><#66366#> can be evenly divided by every number, so we have found our answer: <#66367#><#34250#>6<#34250#><#66367#>. Working through the example not only explains the idea but also suggests how to discover the case with a trivial solution. When the smaller of the two numbers is <#66368#><#34251#>0<#34251#><#66368#>, the result is the larger number. Putting everything together, we get the following definition:
<#71478#>;; <#66369#><#34256#>gcd-generative<#34256#> <#34257#>:<#34257#> <#34258#>N<#34258#><#34259#>[<#34259#><#34260#>;SPMgt;=<#34260#> <#34261#>1]<#34261#> <#34262#>N<#34262#><#34263#>[<#34263#><#34264#>;SPMgt;=1]<#34264#> <#34265#><#34265#><#34266#>-;SPMgt;<#34266#><#34267#><#34267#> <#34268#>N<#34268#><#66369#><#71478#>
<#71479#>;; to find the greatest common divisior of <#66370#><#34269#>n<#34269#><#66370#> and <#66371#><#34270#>m<#34270#><#66371#><#71479#> 
<#71480#>;; <#34271#>generative<#34271#> recursion: <#66372#><#34272#>(gcd<#34272#> <#34273#>n<#34273#> <#34274#>m)<#34274#><#66372#> = <#66373#><#34275#>(gcd<#34275#> <#34276#>n<#34276#> <#34277#>(remainder<#34277#> <#34278#>m<#34278#> <#34279#>n))<#34279#><#66373#> if <#66374#><#34280#>(;SPMlt;=<#34280#> <#34281#>m<#34281#> <#34282#>n)<#34282#><#66374#><#71480#> 
<#34283#>(d<#34283#><#34284#>efine<#34284#> <#34285#>(gcd-generative<#34285#> <#34286#>n<#34286#> <#34287#>m)<#34287#> 
  <#34288#>(l<#34288#><#34289#>ocal<#34289#> <#34290#>((d<#34290#><#34291#>efine<#34291#> <#34292#>(clever-gcd<#34292#> <#34293#>larger<#34293#> <#34294#>smaller)<#34294#> 
            <#34295#>(c<#34295#><#34296#>ond<#34296#> 
              <#34297#>[<#34297#><#34298#>(=<#34298#> <#34299#>smaller<#34299#> <#34300#>0)<#34300#> <#34301#>larger]<#34301#> 
              <#34302#>[<#34302#><#34303#>else<#34303#> <#34304#>(clever-gcd<#34304#> <#34305#>smaller<#34305#> <#34306#>(remainder<#34306#> <#34307#>larger<#34307#> <#34308#>smaller))]<#34308#><#34309#>)))<#34309#> 
    <#34310#>(clever-gcd<#34310#> <#34311#>(max<#34311#> <#34312#>m<#34312#> <#34313#>n)<#34313#> <#34314#>(min<#34314#> <#34315#>m<#34315#> <#34316#>n))))<#34316#> 
The <#66375#><#34320#>local<#34320#><#66375#> definition introduces the workhorse of the function: <#66376#><#34321#>clever-gcd<#34321#><#66376#>, a function based on generative recursion. Its first line discovers the trivially solvable case by comparing <#66377#><#34322#>smaller<#34322#><#66377#> to <#66378#><#34323#>0<#34323#><#66378#> and produces the matching solution. The generative step uses <#66379#><#34324#>smaller<#34324#><#66379#> as the new first argument and <#66380#><#34325#>(remainder<#34325#>\ <#34326#>larger<#34326#><#34327#> <#34327#><#34328#>smaller)<#34328#><#66380#> as the new second argument to <#66381#><#34329#>clever-gcd<#34329#><#66381#>, exploiting the above equation. If we now use <#66382#><#34330#>gcd-generative<#34330#><#66382#> with our complex example from above:
<#34335#>(gcd-generative<#34335#> <#34336#>101135853<#34336#> <#34337#>45014640)<#34337#>
we see that the response is nerly instantaneous. A hand-evaluation shows that <#66383#><#34341#>clever-gcd<#34341#><#66383#> recurs only nine times before it produces the solution: <#66384#><#34342#>177<#34342#><#66384#>. In short, the use of generative recursion has helped find us a much faster solution to our problem.
<#34345#>Exercise 26.3.2<#34345#> Formulate informal answers to the four key questions for <#66385#><#34347#>gcd-generative<#34347#><#66385#>.~ external Solution<#66386#><#66386#> <#34353#>Exercise 26.3.3<#34353#> Enter <#66387#><#34355#>gcd-generative<#34355#><#66387#> into the <#34356#>Definitions<#34356#> window and evaluate <#66388#><#34357#>(time<#34357#>\ <#34358#>(gcd-generative<#34358#>\ <#34359#>101135853<#34359#>\ <#34360#>45014640))<#34360#><#66388#> in the <#34361#>Interactions<#34361#> window. Evaluate <#66389#><#34362#>(clever-gcd<#34362#>\ <#34363#>101135853<#34363#>\ <#34364#>45014640)<#34364#><#66389#> by hand. Show only those lines that introduce a new recursive call to <#66390#><#34365#>clever-gcd<#34365#><#66390#>.~ external Solution<#66391#><#66391#> <#34371#>Exercise 26.3.4<#34371#> Formulate a termination argument for <#66392#><#34373#>gcd-generative<#34373#><#66392#>.~ external Solution<#66393#><#66393#>
Considering the above example, it is tempting to develop functions using generative recursion. After all, they produce answers faster! This judgement is too rash for three reasons. First, even a well-designed algorithm isn't always faster than an equivalent structurally recursive function. For example, <#66394#><#34381#>quick-sort<#34381#><#66394#> wins only for large lists; for small ones, the standard <#66395#><#34382#>sort<#34382#><#66395#> function is faster. Worse, a badly designed algorithm can wreck havoc on the performance of a program. Second, it is typically easier to design a function using the recipe for structural recursion. Conversely, designing an algorithm requires an idea of how to generate new, smaller problems, a step that often requires deep mathematical insight. Finally, people who read functions can easily understand structurally recursive functions, even without much documentation. To understand an algorithm, the generative step must be well-explained, and even with a good explanation, it may still be difficult to grasp the idea. Experience shows that most functions in a program employ structural recursion; only a few exploit generative recursion. When we encounter a situation where a design could use either the recipe for structural or generative recursion, the best approach is often to start with a structural version. If it turns out to be too slow, the alternative design using generative recursion should be explored. If it is chosen, it is important to document the problem generation with good examples and to give a good termination argument.
<#34385#>Exercise 26.3.5<#34385#> Evaluate
<#34391#>(quick-sort<#34391#> <#34392#>(list<#34392#> <#34393#>10<#34393#> <#34394#>6<#34394#> <#34395#>8<#34395#> <#34396#>9<#34396#> <#34397#>14<#34397#> <#34398#>12<#34398#> <#34399#>3<#34399#> <#34400#>11<#34400#> <#34401#>14<#34401#> <#34402#>16<#34402#> <#34403#>2))<#34403#>
by hand. Show only those lines that introduce a new recursive call to <#66396#><#34407#>quick-sort<#34407#><#66396#>. How many recursive applications of <#66397#><#34408#>quick-sort<#34408#><#66397#> are required? How many recursive applications of <#66398#><#34409#>append<#34409#><#66398#>? Suggest a general rule for a list of length <#66399#><#34410#>N<#34410#><#66399#>. Evaluate
<#34415#>(quick-sort<#34415#> <#34416#>(list<#34416#> <#34417#>1<#34417#> <#34418#>2<#34418#> <#34419#>3<#34419#> <#34420#>4<#34420#> <#34421#>5<#34421#> <#34422#>6<#34422#> <#34423#>7<#34423#> <#34424#>8<#34424#> <#34425#>9<#34425#> <#34426#>10<#34426#> <#34427#>11<#34427#> <#34428#>12<#34428#> <#34429#>13<#34429#> <#34430#>14)<#34430#>
by hand. How many recursive applications of <#66400#><#34434#>quick-sort<#34434#><#66400#> are required? How many recursive applications of <#66401#><#34435#>append<#34435#><#66401#>? Does this contradict the first part of the exercise?~ external Solution<#66402#><#66402#> <#34441#>Exercise 26.3.6<#34441#> Enter <#66403#><#34443#>sort<#34443#><#66403#> and <#66404#><#34444#>quick-sort<#34444#><#66404#> into the <#34445#>Definitions<#34445#> window. Test the functions and then explore how fast each works on various lists. The experiment should confirm the claim that the plain <#66405#><#34446#>sort<#34446#><#66405#> function wins (in many comparisons) over <#66406#><#34447#>quick-sort<#34447#><#66406#> for short lists and vice versa. Determine the cross-over point. Then build a <#66407#><#34448#>sort-quick-sort<#34448#><#66407#> function that behaves like <#66408#><#34449#>quick-sort<#34449#><#66408#> for large lists and switches over to the plain <#66409#><#34450#>sort<#34450#><#66409#> function for lists below the cross-over point. <#34451#>Hints:<#34451#> (1) Use the ideas of exercise~#exquicksorttime1#34452> to create test cases. (2) Develop <#66410#><#34453#>create-tests<#34453#><#66410#>, a function that creates large test cases randomly. Then evaluate
<#34458#>(define<#34458#> <#34459#>test-case<#34459#> <#34460#>(create-tests<#34460#> <#34461#>10000))<#34461#>
<#34462#>(collect-garbage)<#34462#> 
<#34463#>(time<#34463#> <#34464#>(sort<#34464#> <#34465#>test-case))<#34465#> 
<#34466#>(collect-garbage)<#34466#> 
<#34467#>(time<#34467#> <#34468#>(quick-sort<#34468#> <#34469#>test-case))<#34469#> 
The uses of <#66411#><#34473#>collect-garbage<#34473#><#66411#> helps DrScheme deal with large lists.~ external Solution<#66412#><#66412#>