The Rosettacode site - https://rosettacode.org/wiki/Category:BBC_BASIC has a category dedicated to BBC BASIC. The version I am talking about of course is Richard Russell's excellent BBC BASIC for Windows. The site lists a huge resource of BASIC programs arranged in alphabetic order. Quite a few will work in RISC OS BASIC unmodified and others need some simple tweaks to make then run. This is one of the best resources available for beginners. It contains some superb examples of string algorithms based on functions (DEF FN). I'm assuming that Richard wrote a significant number of them. There are some very nice features in the Windows version. Its a shame RISC OS users don't have the EXIT keyword for use in loops for example. Some of the examples require libraries which I believe are only available by purchasing BBC BASIC for Windows.. Some examples... Fractual tree, Greatest element of a list, Leap year, Metronome, Old lady swallowed a fly, Ordered words, Palindrome detection, Pangram checker, Pascal's triangle (one of my favourites - so simple and cleverly programmed), Reverse a string, Roman numerals/Decode/Encode, Siepinski triangle. Each project (537 when I last looked) is coded in numerous other languages (C, C++, Lua, Python might interest RISC OS users) - fascinating for programming geeks ;-) Anyone tell me why so many? Richard

0 |

9/11/2016 12:00:55 PM

Richard Ashbery wrote: > The Rosettacode site - > https://rosettacode.org/wiki/Category:BBC_BASIC has a category > dedicated to BBC BASIC. The version I am talking about of course is > Richard Russell's excellent BBC BASIC for Windows. The site lists a > huge resource of BASIC programs arranged in alphabetic order. Quite > a few will work in RISC OS BASIC unmodified and others need some > simple tweaks to make then run. This is one of the best resources > available for beginners. It contains some superb examples of string > algorithms based on functions (DEF FN). It is interesting, although I have identified some routines that are less than optimal lurking there. As an example, here is the Leap Year function you mention: DEFFNleap(yr%) = (yr% MOD 4 = 0) AND ((yr% MOD 400 = 0) OR (yr% MOD 100 <> 0)) That requires all the conditions to be evaluated before combining them. Contrast with: DEFFNleap(yr%) IF yr% MOD 400 ELSE =TRUE IF yr% MOD 100 ELSE =FALSE IF yr% MOD 4 ELSE =TRUE =FALSE That runs about 25% faster for the non-leap-year case, but more than 50% for the 400 year case. I also think it has more clarity. > I'm assuming that Richard wrote a significant number of them. I think so. He originally drew my attention to the site. > Its a shame RISC OS users don't have the EXIT keyword for use in > loops for example. I wrote a simple version of that for Basalt with ARM BASIC a very long time ago, before that was in BB4W, I think. However, it is relatively easy to arrange a program to exit a loop by enclosing within a PROC. > Reverse a string This is another that is less than optimal: DEFFNreverse(A$) LOCAL B$, C% FOR C% = LEN(A$) TO 1 STEP -1 B$ += MID$(A$,C%,1) NEXT =B$ Continuously incrementing a string in this way is not efficient in BASIC. This alternative requires a 256-byte buffer at b% to exist, which will usually be the case in wimp programs, or it could be a DIM LOCAL in RISC OS 5: DEFFNreverse(a$) LOCAL i%,j%,l% $b%=a$ l%=LEN$b%:j%=b%+l% FOR i%=b% TO b%+l%/2 j%-=1 SWAP ?i%,?j% NEXT i% =$b% For short strings there is only a few percent advantage, but for longer strings, of say 50 characters, it is at least twice as fast. I have recently added this facility to SWAP$ in Basalt, but not released. It is at least 20 times as fast. ;-)

0 |

9/11/2016 3:58:49 PM

On 11/09/16 16:58, Steve Drain wrote: > Richard Ashbery wrote: [Referring to the rosettacode.org site] >> Reverse a string > This is another that is less than optimal: > > DEFFNreverse(A$) > LOCAL B$, C% > FOR C% = LEN(A$) TO 1 STEP -1 > B$ += MID$(A$,C%,1) > NEXT > =B$ > Continuously incrementing a string in this way is not efficient in BASIC. > This alternative requires a 256-byte buffer at b% to exist, which will > usually be the case in wimp programs, or it could be a DIM LOCAL in RISC > OS 5: If you're going to offer up a routine that relies on an externally allocated block, that block ought to be an argument to the definition - so it should be DEFFNreverse(a$,b%) In either case some kind of sanity check should be performed - the caller could accidentally put something that evaluates to zero there, for example. However, even with some decent checking, something that isn't a pointer at all could be passed. But at least if the value is passed as an argument, it prompts the programmer to *consider* what that argument should be. > DEFFNreverse(a$) > LOCAL i%,j%,l% > $b%=a$ > l%=LEN$b%:j%=b%+l% > FOR i%=b% TO b%+l%/2 > j%-=1 > SWAP ?i%,?j% > NEXT i% > =$b% I like that one of the comments you made about the leap year alternative you offered is that it's clearer - then you go and offer *that* for the string reverse function. Okay, it's clear to me - and a lot reading this group I should think, but still... p: !cinorI -- Vince M Hudd Soft Rock Software

0 |

9/11/2016 7:08:30 PM

On 11 Sep, Vince M Hudd wrote in message <CNGdnQ5NNc0yNkjKnZ2dnUU78TnNnZ2d@giganews.com>: > On 11/09/16 16:58, Steve Drain wrote: > > Richard Ashbery wrote: > > [Referring to the rosettacode.org site] > > > > Reverse a string > > > This is another that is less than optimal: > > > > DEFFNreverse(A$) > > LOCAL B$, C% > > FOR C% = LEN(A$) TO 1 STEP -1 > > B$ += MID$(A$,C%,1) > > NEXT > > =B$ > > > Continuously incrementing a string in this way is not efficient in > > BASIC. True. On a quick test, using a Beagleboard to reverse a string 100,000 times, this takes ~235cs. But you could avoid that by doing DEFFNreverse(A$) LOCAL B$, C%, D% B$ = STRING$(LEN(A$), " ") D% = 1 FOR C% = LEN(A$) TO 1 STEP -1 MID$(B$,D%,1) = MID$(A$,C%,1) D% += 1 NEXT =B$ For the same test, this takes ~160cs. > > This alternative requires a 256-byte buffer at b% to exist, which will > > usually be the case in wimp programs, or it could be a DIM LOCAL in RISC > > OS 5: > > If you're going to offer up a routine that relies on an externally > allocated block, that block ought to be an argument to the definition - so > it should be DEFFNreverse(a$,b%) [snip] > > DEFFNreverse(a$) > > LOCAL i%,j%,l% > > $b%=a$ > > l%=LEN$b%:j%=b%+l% > > FOR i%=b% TO b%+l%/2 > > j%-=1 > > SWAP ?i%,?j% > > NEXT i% > > =$b% This method, using the global block, takes ~150cs. And if you take into account Vince's not-unreasonable concern, and assume RISC OS 5, you get this DEFFNreverse(a$) LOCAL b%,i%,j%,l% DIM b% LOCAL 255 $b%=a$ l%=LEN$b%:j%=b%+l% FOR i%=b% TO b%+l%/2 j%-=1 SWAP ?i%,?j% NEXT i% =$b% which, including the local DIM, takes ~160cs. -- Steve Fryatt - Leeds, England http://www.stevefryatt.org.uk/

0 |

9/11/2016 8:10:50 PM

Vince M Hudd wrote: > Steve Drain wrote: >> This alternative requires a 256-byte buffer at b% to exist, which will >> usually be the case in wimp programs, or it could be a DIM LOCAL in RISC >> OS 5: > > If you're going to offer up a routine that relies on an externally > allocated block, that block ought to be an argument to the definition - > so it should be DEFFNreverse(a$,b%) Yes, that was wrong. I was translating from using a DIM LOCAL. There is a trick that uses END for the buffer which I have used below. ;-) >> DEFFNreverse(a$) >> LOCAL i%,j%,l% >> $b%=a$ >> l%=LEN$b%:j%=b%+l% >> FOR i%=b% TO b%+l%/2 >> j%-=1 >> SWAP ?i%,?j% >> NEXT i% >> =$b% > > I like that one of the comments you made about the leap year alternative > you offered is that it's clearer - then you go and offer *that* for the > string reverse function. Agreed, that is not at all as clear as using string keywords, but you do gain speed. I dug these out from tests I did quite a long time ago without thinking. Perhaps some better-named variables would help: DEFFNreverse(a$) LOCAL string%,ptr%,end%,length% string%=END:REM buffer at start of free space $string%=a$ length%=LENa$ end%=string%+length% FOR ptr%=string% TO string%+length%/2 end%-=1 SWAP ?ptr%,?end% NEXT ptr% =$string% I hope that helps. ;)

0 |

9/11/2016 8:26:20 PM

Steve Fryatt wrote: > But you could avoid that by doing > > DEFFNreverse(A$) > LOCAL B$, C%, D% > B$ = STRING$(LEN(A$), " ") > D% = 1 > FOR C% = LEN(A$) TO 1 STEP -1 > MID$(B$,D%,1) = MID$(A$,C%,1) > D% += 1 > NEXT > =B$ > > For the same test, this takes ~160cs. Agreed. I had something very similar in my old tests, but for me the buffer method just shaved it. Using the MID$ pseudo-variable construction is often overlooked. Either beats multiple concatenations, especially as the target string gets longer.

0 |

9/11/2016 8:37:10 PM

In article <mpro.odcw1q01fvhzi01xk.news@stevefryatt.org.uk>, Steve Fryatt <news@stevefryatt.org.uk> wrote: > On 11 Sep, Vince M Hudd wrote in message > <CNGdnQ5NNc0yNkjKnZ2dnUU78TnNnZ2d@giganews.com>: > DEFFNreverse(A$) > LOCAL B$, C%, D% > B$ = STRING$(LEN(A$), " ") > D% = 1 > FOR C% = LEN(A$) TO 1 STEP -1 > MID$(B$,D%,1) = MID$(A$,C%,1) > D% += 1 > NEXT > =B$ > For the same test, this takes ~160cs. To my mind it is much easier to see what the original Rosettacode is doing because only two string keywords LEN and MID$ are used. It may be slower but dead simple to understand. Steve - I know your example works but can you explain... MID$(B$,D%,1) = MID$(A$,C%,1) My feeling is that Richard (the other one) wanted to keep the examples simple to the point where anyone with some BASIC knowledge can understand the code without REMs. Speed was not an issue. You can come back to them months in the future and still have some understanding what they do. Have a look at Pascal's Triangle - a single line... acc% = acc% * (row - element%) / element% performs the calculation for all rows - this is a superb example of 'KISS'. Not sure about the + 0.5 on the end - I just deleted it. > And if you take into account Vince's not-unreasonable concern, and assume > RISC OS 5, you get this > DEFFNreverse(a$) > LOCAL b%,i%,j%,l% > DIM b% LOCAL 255 > $b%=a$ > l%=LEN$b%:j%=b%+l% > FOR i%=b% TO b%+l%/2 > j%-=1 > SWAP ?i%,?j% > NEXT i% > =$b% > which, including the local DIM, takes ~160cs. Harder for a beginner to understand though. Richard

0 |

9/13/2016 5:19:24 PM

In article <55bde9548ebasura@invalid.addr.uk>, Richard Ashbery <basura@invalid.addr.uk> wrote: > The Rosettacode site - https://rosettacode.org/wiki/Category:BBC_BASIC > has a category dedicated to BBC BASIC. The version I am talking about > of course is Richard Russell's excellent BBC BASIC for Windows. The > site lists a huge resource of BASIC programs arranged in alphabetic > order. Quite a few will work in RISC OS BASIC unmodified and others > need some simple tweaks to make then run. This is one of the best > resources available for beginners. It contains some superb examples of > string algorithms based on functions (DEF FN). > I'm assuming that Richard wrote a significant number of them. I love this site and regularly use it as a source of inspiration for other stuff. I added some of the BBC BASIC sorting routines. (And also the same routines in Cobol!) I've still got a few I should add, just never got a round tuit. Dave -- Dave Stratford - ZFCB http://daves.orpheusweb.co.uk/

0 |

9/13/2016 8:36:44 PM

On 13 Sep, Richard Ashbery wrote in message <55bf0e2903basura@invalid.addr.uk>: > In article <mpro.odcw1q01fvhzi01xk.news@stevefryatt.org.uk>, > Steve Fryatt <news@stevefryatt.org.uk> wrote: > > On 11 Sep, Vince M Hudd wrote in message > > <CNGdnQ5NNc0yNkjKnZ2dnUU78TnNnZ2d@giganews.com>: > > > DEFFNreverse(A$) > > LOCAL B$, C%, D% > > B$ = STRING$(LEN(A$), " ") > > D% = 1 > > FOR C% = LEN(A$) TO 1 STEP -1 > > MID$(B$,D%,1) = MID$(A$,C%,1) > > D% += 1 > > NEXT > > =B$ > > > For the same test, this takes ~160cs. > > To my mind it is much easier to see what the original Rosettacode is doing > because only two string keywords LEN and MID$ are used. It may be slower > but dead simple to understand. Possibly. I'd argue that my version deals with the other Steve's concern about reallocating string variables while being considerably more readable than his solution, however. > Steve - I know your example works but can you explain... > MID$(B$,D%,1) = MID$(A$,C%,1) When assigned to, LEFT$(), MID$() and RIGHT$() allow you to overwrite specific parts of a string. In the example above, you're taking the right-most character of the original and placing it in the left-most position of the result. You then step one position in from each end, and repeat... At the start, after calling FNreverse("ABCDEFG"), A$="ABCDEFG" and B$=" " after 1 cycle, A$="ABCDEFG" and B$="G " after 2 cycles, A$="ABCDEFG" and B$="GF " and so on. The key point is that B$ is initialised to the required length with spaces and then always remains the same length, which means that after being allocated there's no time wasted shuffling memory around. In the Rosetta original, this happens instead: A$="ABCDEFG" and B$="" after 1 cycle, A$="ABCDEFG" and B$="G" after 2 cycles, A$="ABCDEFG" and B$="GF" and so on. Each cycle, B$ gets one character longer, which is one of the worst things that you can ask BBC BASIC to do. Does it matter? Probably not (although you might be surprised how much little performance gains can make a difference). I was responding to Steve's suggestion that indirection was the way forward, however. > Not sure about the + 0.5 on the end - I just deleted it. That's probably quite important, actually. It's also a very common idiom in BASIC, and incredibly useful in many places -- so well worth understanding. acc% = acc% * (row% - element%) / element% + 0.5 acc% * (row% - element%) / element% is going to be a floating point value due to the division. Assigning it to acc% clearly turns it back into an integer, and that's a shorthand way of saying acc% = INT(acc% * (row% - element%) / element% + 0.5) HELP INT tells us that INT() "gives us the nearest integer less than or equal to the number. So while INT(n) rounds down from n, INT(n + 0.5) rounds n to the nearest integer. It's probably clearest with an example: FOR value = 0 TO 2 STEP 0.1 PRINT value, INT(value), INT(value + 0.5) NEXT value When run, that gives us 0 0 0 0.1 0 0 0.2 0 0 0.3 0 0 0.4 0 0 0.5 0 1 0.6 0 1 0.7 0 1 0.8 0 1 0.9 0 1 1 1 1 1.1 1 1 1.2 1 1 1.3 1 1 1.4 1 1 1.5 1 2 1.6 1 2 1.7 1 2 1.8 1 2 1.9 1 2 > > And if you take into account Vince's not-unreasonable concern, and > > assume RISC OS 5, you get this > > > DEFFNreverse(a$) > > LOCAL b%,i%,j%,l% > > DIM b% LOCAL 255 > > $b%=a$ > > l%=LEN$b%:j%=b%+l% > > FOR i%=b% TO b%+l%/2 > > j%-=1 > > SWAP ?i%,?j% > > NEXT i% > > =$b% > > > which, including the local DIM, takes ~160cs. > > Harder for a beginner to understand though. That was kind of the point of my original post... ;-) -- Steve Fryatt - Leeds, England http://www.stevefryatt.org.uk/

0 |

9/13/2016 11:15:59 PM

In article <mpro.odgtya07al2bi01wh.news@stevefryatt.org.uk>, Steve Fryatt <news@stevefryatt.org.uk> wrote: > On 13 Sep, Richard Ashbery wrote in message [snip] > > > DEFFNreverse(A$) > > > LOCAL B$, C%, D% > > > B$ = STRING$(LEN(A$), " ") > > > D% = 1 > > > FOR C% = LEN(A$) TO 1 STEP -1 > > > MID$(B$,D%,1) = MID$(A$,C%,1) > > > D% += 1 > > > NEXT > > > =B$ > > > > > For the same test, this takes ~160cs. > > > > To my mind it is much easier to see what the original Rosettacode is doing > > because only two string keywords LEN and MID$ are used. It may be slower > > but dead simple to understand. > Possibly. I'd argue that my version deals with the other Steve's concern > about reallocating string variables while being considerably more readable > than his solution, however. Now I see what you're program is doing I agree. > > Steve - I know your example works but can you explain... > > MID$(B$,D%,1) = MID$(A$,C%,1) > When assigned to, LEFT$(), MID$() and RIGHT$() allow you to overwrite > specific parts of a string. In the example above, you're taking the > right-most character of the original and placing it in the left-most > position of the result. You then step one position in from each end, and > repeat... > At the start, after calling FNreverse("ABCDEFG"), > A$="ABCDEFG" and B$=" " > after 1 cycle, A$="ABCDEFG" and B$="G " > after 2 cycles, A$="ABCDEFG" and B$="GF " > and so on. > The key point is that B$ is initialised to the required length with spaces > and then always remains the same length, which means that after being > allocated there's no time wasted shuffling memory around. In the Rosetta > original, this happens instead: > A$="ABCDEFG" and B$="" > after 1 cycle, A$="ABCDEFG" and B$="G" > after 2 cycles, A$="ABCDEFG" and B$="GF" > and so on. > Each cycle, B$ gets one character longer, which is one of the worst things > that you can ask BBC BASIC to do. > Does it matter? Probably not (although you might be surprised how much > little performance gains can make a difference). I was responding to Steve's > suggestion that indirection was the way forward, however. Many thanks for taking time to explain this - I was in a bit of a fog - which seems more common these days. I can see that with a very large string this could have a significant impact on the calculation speed. I hadn't twigged that the B$ unnecessarily 'accumulates' the characters each time through the loop in Richard's code and your example is a neat way of preventing this. > > Not sure about the + 0.5 on the end - I just deleted it. > That's probably quite important, actually. It's also a very common idiom in > BASIC, and incredibly useful in many places -- so well worth understanding. > acc% = acc% * (row% - element%) / element% + 0.5 > acc% * (row% - element%) / element% is going to be a floating point value > due to the division. Assigning it to acc% clearly turns it back into an > integer, and that's a shorthand way of saying > acc% = INT(acc% * (row% - element%) / element% + 0.5) > HELP INT tells us that INT() "gives us the nearest integer less than or > equal to the number. So while INT(n) rounds down from n, INT(n + 0.5) rounds > n to the nearest integer. > It's probably clearest with an example: > FOR value = 0 TO 2 STEP 0.1 > PRINT value, INT(value), INT(value + 0.5) > NEXT value [snip interesting output] So for a value of 1.9 the INT value is 1 but true value is 2. That's a significant difference. Once again a thing I hadn't understood. As a matter of interest it wouldn't actually make any difference in Pascal's Triangle because the numbers are so big anyway but I take your point and Richard was quite correct in taking it into consideration. Again Steve - many thanks for your explanations. Richard

0 |

9/14/2016 7:57:45 PM

In message <55bfa07dc9basura@invalid.addr.uk> on 14 Sep 2016 Richard Ashbery wrote: > > > Not sure about the + 0.5 on the end - I just deleted it. > > > That's probably quite important, actually. It's also a very common idiom in > > BASIC, and incredibly useful in many places -- so well worth understanding. > > > acc% = acc% * (row% - element%) / element% + 0.5 > > > acc% * (row% - element%) / element% is going to be a floating point value > > due to the division. Assigning it to acc% clearly turns it back into an > > integer, and that's a shorthand way of saying > > > acc% = INT(acc% * (row% - element%) / element% + 0.5) > > > HELP INT tells us that INT() "gives us the nearest integer less than or > > equal to the number. So while INT(n) rounds down from n, INT(n + 0.5) rounds > > n to the nearest integer. > > > It's probably clearest with an example: > > > FOR value = 0 TO 2 STEP 0.1 > > PRINT value, INT(value), INT(value + 0.5) > > NEXT value > > [snip interesting output] > > So for a value of 1.9 the INT value is 1 but true value is 2. That's a > significant difference. Once again a thing I hadn't understood. > > As a matter of interest it wouldn't actually make any difference in > Pascal's Triangle because the numbers are so big anyway but I take > your point and Richard was quite correct in taking it into > consideration. On the contrary, it is when the numbers get big that the rounding errors are more likely to occur because of the limited number of bits available in the mantissa in floating point arithmetic. As it happens, it makes no difference on BBC BASIC V on RISC OS because the numbers get too large for 32-bit integers before any rounding errors come into play. It might make a difference in BASIC VI, or it might help if you wanted to go to very high numbers of rows by using floating point throughout the program. The answer, of course is always supposed to be an integer. If the floating point result is very slightly under, the +0.5 would help set it straight. -- Matthew Phillips Durham

0 |

9/14/2016 10:49:21 PM

Steve Drain wrote: > Steve Fryatt wrote: >> But you could avoid that by doing >> >> DEFFNreverse(A$) >> LOCAL B$, C%, D% >> B$ = STRING$(LEN(A$), " ") >> D% = 1 >> FOR C% = LEN(A$) TO 1 STEP -1 >> MID$(B$,D%,1) = MID$(A$,C%,1) >> D% += 1 >> NEXT >> =B$ >> >> For the same test, this takes ~160cs. > > Agreed. I had something very similar in my old tests, but for me the > buffer method just shaved it. Using the MID$ pseudo-variable > construction is often overlooked. Either beats multiple concatenations, > especially as the target string gets longer. I could have sworn that in my original tests I got figures similar to yours. I returned to look again and found that the method above, or my variant of it, comes out even slower than the concatenation method on my AMX6, with BASIC V v1.59. I cannot explain that. ;-(

0 |

9/16/2016 9:21:57 AM

Richard Ashbery wrote: > To my mind it is much easier to see what the original Rosettacode is > doing because only two string keywords LEN and MID$ are used. It may > be slower but dead simple to understand. [...] > > My feeling is that Richard (the other one) wanted to keep the examples > simple to the point where anyone with some BASIC knowledge can > understand the code without REMs. Speed was not an issue. You can come > back to them months in the future and still have some understanding > what they do. I think there is some merit in that, but my concern is that such a public example should not propagate poor programming techniques. Repeated concatenation is a poor technique in BASIC. Although it is not as clear, buffering a string and manipulating indirected bytes, however you achieve it, is a good technique that is easily available in BBC BASIC, but not in most others versions. I concede that the use of MID$()=, as in Steve Fryatt's example, should be the best choice in this case, except that I am now unsure of its speed - see my other post. > Have a look at Pascal's Triangle - a single line... > acc% = acc% * (row - element%) / element% + 0.5 > performs the calculation for all rows - this is a superb example of > 'KISS'. I disagree. That method might be a good choice if the task were just to print the Nth row of the triangle, but if you are constructing the whole triangle you are better following Pascal's rule and adding the elements from the row above. I wrote this method: DEFPROCPascal(rows%) LOCAL wid%,@%,row%,num%,row%() DIM row%(rows%) wid%=3 @%=wid%*2 row%(1)=1 FOR row%=1 TO rows% PRINT TAB(wid%*(rows%-row%)); FOR num%=row% TO 1 STEP -1 row%(num%)+=row%(num%-1) PRINT row%(num%); NEXT num% NEXT row% PRINT ENDPROC Returning to the site, I see that it is very similar to the generic BASIC method a little above the BBC BASIC one. As for calculating the binomial coefficients in BBC BASIC V, an awareness of the increase in N! (factorial N) with N, and the limits of 5-byte floating point numbers, leads to the fact that N! can only be expressed if N<=33. If a program is doing a lot of factorials, then constructing a look-up table is the best technique, rather than attempting to calculate N! with a - possibly recursive - function. Basalt uses this for its FACT keyword to return N!, so it is very fast. It will also return nPr (permutations) and nCr (combinations). The latter are the binomial coefficients.

0 |

9/16/2016 9:59:33 AM