home *** CD-ROM | disk | FTP | other *** search
/ Archive Magazine 1995 / ARCHIVE95.iso / discs / pipeline / abacus / p_line / Custom05 / ReadMe < prev   
Text File  |  1995-05-13  |  17KB  |  378 lines

  1. %OP%VS4.13 (28-Apr-92), Gerald L Fitton, R4000 5966 9904 9938 
  2. %OP%DP0
  3. %OP%IRY
  4. %OP%PL0
  5. %OP%HM0
  6. %OP%FM0
  7. %OP%BM0
  8. %OP%LM4
  9. %OP%PT1
  10. %OP%PDPipeLine
  11. %OP%WC2,1250,44,1748,0,0,0,0
  12. %CO:A,12,72%
  13. %C%Recursion
  14. %C%by Gerald L Fitton
  15. Keywords:
  16. Recursion deref() Fitton
  17.  
  18. Introduction
  19.  
  20. Those of you who are following this Custom Function series will have 
  21. read the first four articles of this series.  They can be found in the 
  22. directories Custom01, Custom02, Custom03 and Custom04.
  23.  
  24. I have called the custom function language '4ProL' (this is intended to 
  25. be short for PipeDreamá4 Programming Language).  Designing a new 
  26. language such as '4ProL' is always a compromise between:
  27.  
  28. (a) Including commands which provide the flexibility needed to carry 
  29.     out complex or intricate processes quickly and
  30.  
  31. (b) Inherent constraints which encourage those 'good' programming 
  32.     techniques needed to facilitate debugging and program improvements.
  33.  
  34. The compromise is to learn a set of conventions (a sub set of the 
  35. constraints of the language) which encourage 'good' programming and to 
  36. aim to write 'well written' programs.  The conventions which I am 
  37. recommending in this series have been discussed with and approved of by 
  38. Colton Software.
  39.  
  40. If you write 'good' programs using the set of conventions I recommend 
  41. then you will be able to debug and expand your programs easily than if 
  42. you use what I call 'bad' programming techniques.
  43.  
  44. The earlier articles covered the topics Sequence (calling a function 
  45. and returning a result), Conditional execution (If), the use of 
  46. Parameters and Named variables, Local and Global variables and 
  47. Repetition using Forá-áNext loops.
  48.  
  49. In this article I introduce the concept of Recursion.
  50.  
  51.  
  52. My Example
  53.  
  54. For my example I am going to calculate the value of a mathematical 
  55. function which returns the number of ways of selecting, say, a set of 
  56. six lottery numbers from fortyánine numbers or three committee members 
  57. from a short list of ten.  Although somewhat old fashioned these days, 
  58. I shall call the function nCr because, in PipeDream and using ASCII, I 
  59. find it difficult to use the more modern notation for this function.
  60.  
  61. The value of nCr(49,6) is given by: (49*48*47*46*45*44)/(6*5*4*3*2*1).
  62.  
  63. %L%You will notice that there are r (ie 6) numbers on the top
  64. %L%and there are r numbers on the bottom of the fraction.
  65.  
  66. %L%The numbers on the top start at n (ie 49) and work downwards;
  67. %L%those on the bottom start at r (ie 6) and work downwards.
  68.  
  69.  
  70. Using a Forá-áNext Loop
  71.  
  72. Any program which uses recursion can also be written using either 
  73. 'Forá-áNext' or 'Repeat - Until' loops.
  74.  
  75. One method of calculating nCr(49,6) is to work out the top line first, 
  76. ieá(49*48*47*46*45*44), then work out the bottom line, (6*5*4*3*2*1), 
  77. finally dividing the top line by the bottom line.  I don't recommend 
  78. that method because, in some cases (but not this case) it tends to lead 
  79. to very large numbers which might 'run off the end' of the calculator 
  80. (ie overflow).
  81.  
  82. The alternative method of calculating the value of nCr(n,r) is to start 
  83. by calculating n/r.  This is followed by multiplying the answer 
  84. produced by (n-1)/(r-1) and then by (n-2)/(r-2) etc r times.  This is 
  85. the method I shall use since it avoids generating very large numbers 
  86. which have to be divided by each other after calculation.
  87.  
  88. %L%Load the file [nCr_1] by double clicking on it.
  89. %L%As well as [nCr_1] you will load the custom function file [c_nCr].
  90.  
  91. Have a look at the construction of the function "binomial_1".  You will 
  92. see that, as usual, I have declared my local variable, "current_value" 
  93. by naming it before initialising its value to 1.  For the reasons I 
  94. explained in an earlier article it is not possible to declare the 
  95. "loop_counter" as a local variable.
  96.  
  97. For the UK Weekly National Lottery (see the directory Lottery01) the 
  98. number of possible lines is nCr(49,6) ieáná=á49 and rá=á6.  Try these 
  99. values in [nCr_1] and you'll see that the number of possible 
  100. combinations of six numbers from fortyánine is nCr(49,6)á=á ?   Well?  
  101. What is it?
  102.  
  103. You'll see that I've included a dummy loop (using the loop variable 
  104. "loop_counter_2") to slow down the operation so that you can observe 
  105. the calculation proceeding in slot B13.
  106.  
  107.  
  108. Recurrence relationships
  109.  
  110. If you look at the expression for nCr(49,6) you will see that 
  111. nCr(49,6)á=á(49/6)nCr(48,5).  In other words we can express the value 
  112. of nCr(49,6) in terms of nCr(48,5).  In the same way we could express 
  113. nCr(48,5) as (48/5)nCr(47,4) and so on - but not forever!
  114.  
  115. In mathematics (long before computers) this type of general statement 
  116. existed and it is called a recurrence relationship.
  117.  
  118. For the function nCr the recurrence relationship is:
  119.  
  120.  nCr(n,r)á=á(n/r)*nCr(n-1,r-1) for values of r>1.
  121.  
  122. When rá=á1 we have a problem because 1á-á1á=á0 and dividing by 0 is one 
  123. of those things that mathematicians (and computers) find difficult!
  124.  
  125. When rá=á1 the value of nCr((n,1)á=án.
  126.  
  127. This leads us to a mathematical statement which we can write in 
  128. PipeDream format as:
  129.  
  130. áif(r>1,nCr(n,r)á=á(n/r)*nCr(n-1,r-1),n)
  131.  
  132.  
  133. Using Recursion
  134.  
  135. There is a lot of snobbery about recursion.  It is often portrayed as a 
  136. programming feature which can be used only by an expert.
  137.  
  138. Don't you believe it!
  139.  
  140. There are many useful mathematical functions (such as the nCr function 
  141. above) which are capable of simple expression as a recurrence 
  142. relationship but which, in explicit form, look overwhelmingly difficult 
  143. to understand!  In these cases using recursion makes the program easier 
  144. to write and easier to understand.  When a program is easy to 
  145. understand then changing (improving or expanding) it is much easier.  
  146. In those cases I recommend using recursion.
  147.  
  148. Where the reasoning behind the use of recursion is obscure I would 
  149. suggest that the writer is just showing off!
  150.  
  151. There is a drawback to using recursion.  I have to confess that 
  152. recursion often takes longer to evaluate than the explicit (but more 
  153. complicated) version whether you use a spreadsheet or BASIC.
  154.  
  155.  
  156. The Recursive Function
  157.  
  158. Double click on the file [nCr_2].  It uses the custom function 
  159. "binomial_2", the second function contained in the file [c_nCr].
  160.  
  161. As is usual with my tutorials I have included a lot of material within 
  162. the custom function which is not essential to its operation but which, 
  163. I think, aids the understanding of how it all works.
  164.  
  165. The recursive call is made in the line which starts 
  166. "...set_value(answer,ifá.á.á.á)".  This line includes a call to the 
  167. function "binomial_2",  that is to say the function "binomial_2" calls 
  168. itself!  It is this feature of a function calling itself which makes it 
  169. recursive.
  170.  
  171. There is a difference in the arguments of the original function and the 
  172. arguments used in the recursive call.  The original function calculates 
  173. nCr(n,r) whereas the recursive call calculates nCr(n-1,r-1).
  174.  
  175. In fact, after stripping out all the nonáessential parts of the custom 
  176. function nCr, what you are left with is a recursive procedure which 
  177. expresses the fact that
  178.  
  179.  nCr(n,r)á=á(n/r)*nCr(n-1,r-1).
  180.  
  181. The mathematical equation 'nCr(n,r)á=á(n/r)*nCr(n-1,r-1)' is a simple
  182. 'recurrence relationship'.  Such mathematical functions are often 
  183. expressed in BASIC or in spreadsheets as recursive functions just 
  184. because the writing of the 'program' is easier to understand and easier 
  185. to get right first time.
  186.  
  187. I have included in the custom function "binomial_2" a block of local 
  188. variables 'r_on_entry', 'r_on_exit' and 'entry_or_exit' with the 
  189. intention of letting you see the recursive feature in operation.
  190.  
  191. On entry each (recursive) call to the custom function is executed only 
  192. down to the point where it becomes recursive.  I call this 'entering' 
  193. the recursive procedure because you can imagine that each time you 
  194. reach the point of recursion you enter (go into and through) a gateway 
  195. to the next recursion.  When you go through this gateway to the next 
  196. inner incarnation of the function PipeDream stores the 'outer' (old) 
  197. function and displays (on screen) a new, inner incarnation of the 
  198. custom function.  You can see this happening if you observe the value 
  199. of the local variable "r_on_entry".  The early part of the function 
  200. (down to the point of recursion) is executed recursively for reducing 
  201. values of n and r.  During this part of the recursive procedure the 
  202. latter part of the recursive function is never executed.
  203.  
  204. Naturally we can't keep going inwards for ever.  When you set up a 
  205. recursive procedure then, like everything in life, it's important to 
  206. know when to stop!  In the case of nCr we have to stop the recursive 
  207. procedure when the value of rá=á1 because the next incarnation would 
  208. lead us to try to divide by zero.
  209.  
  210. We achieve this halt to the recursion by using an if(,,) function.  The 
  211. innermost incarnation of "binomial_2" fails to call itself because the 
  212. if(r>1,,) ceases to be true.
  213.  
  214. What happens after the innermost incarnation of "binomial_2" has been 
  215. executed?  If I haven't lost you yet then you'll probably realise that 
  216. it is the innermost incarnation of "binomial_2" which reaches the 
  217. '...result(answer)' line before any of the 'outer' incarnations.
  218.  
  219. This value is returned to the next incarnation (going outwards) which 
  220. then completes its execution down to '...result(answer)'.
  221.  
  222. Having 'entered' the recursive procedure r times we must 'unwind' it by 
  223. using the 'exit' part of the custom function r times.  You can see this 
  224. happening by observing the value of the local variable "r_on_exit".
  225.  
  226. One at a time the values of '...result(answer)' are returned until 
  227. every incarnation has been executed.  Finally the last (outermost) 
  228. value is returned to the main spreadsheet.
  229.  
  230. Also you will see that the local variable "entry_or_exit" takes 
  231. appropriate values to show which part of the loop is being executed.
  232.  
  233. So that both the 'entry' and 'exit' procedures can be observed I have 
  234. slowed them down by using a couple of dummy Forá-áNext loops.
  235.  
  236.  
  237. The Lottery
  238.  
  239. Although I deal with the lottery in more detail in the Lottery01 
  240. directory (on another disc) I would like to use the calculation of the 
  241. probability of winning the fixed ú10.00 prize as an example of the use 
  242. of the obscure deref() function.
  243.  
  244. The lottery machine selects six balls from the fortyánine.  You select 
  245. six numbers from the fortyánine.  If three of your six numbers match 
  246. three of the winning numbers then you've won ú10.00.
  247.  
  248. Let's consider building up our own set of six balls.  We must choose 
  249. three balls from the six winning balls.  This gives nCr(6,3) possible 
  250. combinations.  We must also choose a further three from the fortyásix 
  251. losers.  The number of different ways of doing this is nCr(46,3).
  252.  
  253. These two numbers must be multiplied together to give 
  254. nCr(6,3)*nCr(46,3).  Whatever this number is it is the number of 
  255. different lines (of six) which win ú10.00.
  256.  
  257.  
  258. DeRef(slotref)
  259.  
  260. Let me use this calculation to explain something that has puzzled many 
  261. people using the same custom function twice within the same slot.
  262.  
  263. If you use the same custom function twice within the same slot then 
  264. you'll get the wrong answer.  By this I mean that if you try to 
  265. calculate nCr(6,3)*nCr(43,3) in one slot using this version of 
  266. "binomial_2" then you'll find that the answer you get is wrong.  You'll 
  267. get (nCr(43,3))^2 instead!
  268.  
  269. The way around this problem is to use deref() as described below.  I 
  270. would classify this as a bug but it is an obscure one with an obscure 
  271. work around.  What I have to say applies to all custom functions and 
  272. not only recursive functions - we'll use a simple custom function to 
  273. demonstrate the effect and the work around.
  274.  
  275. Double click on the file [Add] and you will load it and two similar 
  276. custom function files [c1_Add] and [c2_Add].  Have a look at the two 
  277. slots [Add]E2 and [Add]E3.  The only difference is that slot E2 uses 
  278. the function you'll find in [c2_Add] and E3 uses a function of the same 
  279. name in [c1_Add].  You will appreciate that [c2_Add] gives the correct 
  280. answer but that [c1_Add] doesn't.  In fact, what the function in 
  281. [c1_Add] does is to add [c1_Add]add_together(C3) to itself.  The answer 
  282. is always double the value in [Add]C3!
  283.  
  284. The only difference between the two custom functions is that in 
  285. [c1_Add]A13 you'll find ...result(answer) whereas in [c2_Add]A13 you'll 
  286. find that the deref() function is included as ...result(deref(answer)).
  287.  
  288. I still haven't worked out exactly why I need the deref() but I do 
  289. remember Robert Macmillan explaining it to me once!  After he'd 
  290. explained it to me he said "You know Gerald, I think that's the first 
  291. time I've understood the way deref() works!"  He was referring to an 
  292. application which passed an array to the custom function and returned 
  293. another array!  Often you need to use deref() in array operations if 
  294. you wish to get the correct answer!  More of this another day.
  295.  
  296. You will find some (very limited) information about the deref() 
  297. function in the PipeDream handbook but it won't tell you that you 
  298. should use deref() as a matter of course when using custom functions 
  299. that might be repeated within the same slot.
  300.  
  301. Let's have a look at the way in which the slot [Add]E2 is evaluated and 
  302. I'll explain my limited understanding of what is going on.  During the 
  303. first part of the calculation the custom function [c2_Add] is evaluated 
  304. and the value of the local variable 'answer' is stored not in 
  305. [c2_Add]B9 but in PipeDream's private workspace.  Without the deref() 
  306. function this value in the private workspace would be overwritten when 
  307. the custom function is run the second time.
  308.  
  309.  
  310. A Double Recursion
  311.  
  312. For interest only I include a BASIC program on this disc which I wrote 
  313. originally in Novemberá1987 called [Needles].  It is based on an 
  314. ancient puzzle.  You have to transfer a block of discs from one needle 
  315. to another without placing a large disc on top of a smaller disc.  The 
  316. BASIC program uses the same recursive procedure, PROCmove, twice within 
  317. itself.
  318.  
  319. If you look at the parameters passed to the recursive parts of the 
  320. procedure you'll see that it implies that a block of discs is moved 
  321. from needleá1 to the spare needle so that the 'last' disc on needleá1 
  322. is uncovered.  After moving this last disc from needleá1 to needleá2 
  323. the block of discs is moved from the spare needle on top of the last 
  324. disc (now on needleá2).
  325.  
  326. By defining the procedure as a recursive one we avoid worrying about 
  327. the finer detail of moving the block of discs from needleá1 to the 
  328. spare needle.  We stop 'entering' the recursion when we have moved the 
  329. last disc of the block we want to move.
  330.  
  331. Now I'm not pretending that this is a simple problem but you might like 
  332. to study it as an example of recursion.  If you are able to find a way 
  333. of recording the moves as a PipeDream array or spreadsheet then I'd 
  334. like to hear from you.
  335.  
  336.  
  337. Summary
  338.  
  339. Let me start by repeating advice about variables from an earlier 
  340. article.  There are three categories of variable - local variables, 
  341. parameters and global variables.
  342.  
  343. Parameters are values passed to custom functions as arguments of the 
  344. function.  The value of a parameter should not be changed within a 
  345. custom function.
  346.  
  347. Local variables are declared within a custom function using 
  348. set_name(,).  They should have no meaning outside the custom function 
  349. in which they are used.
  350.  
  351. What I have called a 'self contained' custom function is one in which 
  352. the only variables are parameters and local variables (ie no global 
  353. variables).  Such custom functions can be use by anybody from any 
  354. calling document (just like anybody can use the square root function) 
  355. without knowing why it works.  Furthermore, 4ProL will not get confused 
  356. if the same name is used for local variables within different custom 
  357. functions which are all 'working simultaneously'.
  358.  
  359. A recursive custom function must be 'self contained' in the way in 
  360. which I have described otherwise different incarnations of the custom 
  361. function will get confused about what value to give the variable.
  362.  
  363. Use the deref() function to 'disconnect' separate evaluations of the 
  364. same custom function within a single slot (or array).
  365.  
  366. You can always convert a recursive function into a forá-ánext or 
  367. repeatá-áuntil loop.  Generally, if the function is based on a 
  368. recurrence relationship then it is easier to write and understand the 
  369. custom function if it is written as a recursive function mirroring the 
  370. recurrence relationship!
  371.  
  372. Many would disagree about this 'ease of writing' statement but I 
  373. believe that is because they've not had recursive procedures explained 
  374. to them as a way of converting a recurrence relationship such as 
  375. nCr(n,r)á=á(n/r)*nCr(n-1,r-1) to a custom function.  I would say, 
  376. "First write down your 'recurrence relationship' and then write your 
  377. recursive custom function."
  378.