recently there was a big fuzz on digg.com and the like about this
article:
http://tickletux.wordpress.com/2007/01/24/using-fizzbuzz-to-find-developers-who-grok-coding/
The author suggests that most self-called programmers can't solve the
following problem:
Write a program that prints the numbers from 1 to 100. But for
multiples of three print "Fizz" instead of the number and for the
multiples of five print "Buzz". For numbers which are multiples of
both three and five print "FizzBuzz".
As I am interested but not yet very proficient in stack based
programming languages, I ask you: how would you solve this problem in
FORTH?
Keo
Why are you interested in my solution?
Why don't you give it a try?
Then we'll discuss it further.
--
Coos
CHForth, 16 bit DOS applications
http://home.hccnet.nl/j.j.haak/forth.html
> As I am interested but not yet very proficient in stack based
> programming languages, I ask you: how would you solve this problem in
> FORTH?
>
Did you study the solution given in the post # 53?
: fizz? 3 mod 0= dup if ." fizz" endif ;
: buzz? 5 mod 0= dup if ." buzz" endif ;
: doit 101 1 do
i fizz?
i buzz?
or 0= if i . endif
cr
loop
;
<keo...@gmail.com> schrieb im Newsbeitrag
news:1172704579.4...@z35g2000cwz.googlegroups.com...
The code is over-factored, making it hard to read. There is also too
much stack noise: this problem really doesn't need DUP SWAP etc.
A simple solution is
: bang
100 1 do
i 15 mod 0= if ." FizzBuzz " else
i 3 mod 0= if ." Fizz " else
i 5 mod 0= if ." Buzz " else
i . then then then
loop ;
There are many more complex and efficient solutions possible.
( "the numbers from 1 to 100" is ambiguous. I'm assuming a half-open
interval. )
Andrew.
John Passaniti's solution is a bit cleaner IMO. He factors fizz and
buzz, which eliminates the need for 15 mod.
-Doug
And adds a couple of DUPs and an OR. Shrug.
It's quite a clever solution. There's not much in it, really, and
either would be from my POV perfectly acceptable.
Andrew.
I don't see any problem with that code. It looks fine to me.
> A simple solution is
>
> : bang
> 100 1 do
> i 15 mod 0= if ." FizzBuzz " else
> i 3 mod 0= if ." Fizz " else
> i 5 mod 0= if ." Buzz " else
> i . then then then
> loop ;
Your solution looks fine to me too.
Your code brings up something for me. I notice those three then then
thens. Forth doesn't usually have a way to skip over the rest of a
loop and then repeat. We have ways to break out of the loop, but not a
command to skip to the end of the loop. So we have ugly things like
then then then to do it, both in DO loops and in BEGIN loops.
I've never felt the need for an improvement there. Multiple THENs
don't actually cause a performance hit, only a very slight compile-
time hit. Wil Baden made his THENS command which palliates it.
Somehow I never noticed until now that you can get a similar result
with multiple BEGINs.
: bang1
0 BEGIN BEGIN 1+ cr
dup 3 mod DUP 0= if ." Fizz" then
over 5 mod dup 0= if ." Buzz" then
or 0= UNTIL
dup .
dup 100 >
UNTIL ;
I certainly don't want to propose this as clear writing or even
adequate code (like, the method would run over if you changed the
limit to 99BEG), it's just that I never thought of this as a single
loop with a continuation. If you had a word that was just like UNTIL
except it left a copy of the dest, it would effectively skip to (but
not past) the end of the loop on failure or execute the rest of the
loop on success.
BEGIN ... SKIP-TO-REPEAT ... SKIP-TO-REPEAT ... SKIP-TO-REPEAT ...
WHILE ... REPEAT
Again, I've never felt the need to have something like this, I've
always just accepted THEN THEN THEN as good enough.
Additional control structure words might someimes make the flow of
control clearer, provided we all remembered what they did. What we
already have is adequate.
: bang2
101 0 do
i 3 mod 0= 1 and
i 5 mod 0= 2 and or
case
0 of i . endof
1 of Fizz endof
2 of Buzz endof
3 of FizzBuzz endof
endcase
loop ;
This one doesn't read better either, but at least the most common
cases are tested the most often. A jump table might be more efficient
but also less readable.
: DUP. dup . ;
CREATE TABLE
' DUP. ,
' Fizz ,
' Buzz ,
' FizzBuzz ,
: do-it
CELLS TABLE + @ EXECUTE ;
: which-action ( n -- n 0|1|2|3 )
dup
i 3 mod 0= 1 and
i 5 mod 0= 2 and or ;
: Bang3
0 begin
1+ dup 101 <
while
which-action do-it
repeat drop ;
I guess when the first methods I learned give a result that isn't too
long, then that's the most readable. There's nothing hard to
understand about nested IF THENs. Just, if they get too complex you
can lose track of how deeply they're nested and which places which
result happens.
> : bang
> 100 1 do
> i 15 mod 0= if ." FizzBuzz " else
> i 3 mod 0= if ." Fizz " else
> i 5 mod 0= if ." Buzz " else
> i . then then then
> loop ;
Factor it and you'll be there. John Passaniti has a good factoring.
> And adds a couple of DUPs and an OR. Shrug.
>
> It's quite a clever solution. There's not much in it, really, and
> either would be from my POV perfectly acceptable.
Yes. It's no big deal one way or the other. My first version looked
more like John's and was coded as fast as I could type it (as I
suspect was the case for you and John and most Forthers). But I do
worry a bit when I see triply nested IF-ELSE-THENs.
In the Mops kernel we have always had NIF, which is the same as 0=
IF. I find NIF to be very useful. Using that and after seeing John's
code, my favorite would be the following:
: fizz? ( n -- flag ) \ flag is 0 if hit
3 mod dup NIF ." Fizz" THEN ;
: buzz? ( n -- flag ) \ flag is 0 if hit
5 mod dup NIF ." Buzz" THEN ;
: FizzBuzz
101 1 DO cr
i fizz?
i buzz?
* IF i . THEN
LOOP ;
-Doug
p.s. Wait! I forgot to use objects! I must be coming down with the
flu or something. ;-)
Nothing wrong with other solutions but a simple solution avoiding
nested ifs etc uses bit vectors. The constants can be stuck in line
hex
1249 constant 3vec
0421 constant 5vec
4000 constant probe
3vec 5vec or constant 3or5vec
decimal
: bang
cr 0 101 1
do
?dup 0= if probe then
3or5vec over and 0= if i . then
3vec over and if ." fizz" then
5vec over and if ." buzz" then
1 rshift cr
loop
drop
;
one reason I find this one well factored is that when I tried, I
found it was easy to make the pretty printing as a separate factor:
: fizz? ( n -- fl ) 3 MOD 0= DUP IF ." Fizz" THEN ;
: buzz? ( n -- fl ) 5 MOD 0= DUP IF ." Buzz" THEN ;
: ?.fizzbuzz-WS ( fl1 fl2 -- fl1 fl2 )
2DUP AND IF CR ELSE 2DUP OR IF SPACE THEN THEN
;
: bang1 ( -- ) 101 1 DO
I fizz? I buzz? ?.fizzbuzz-WS
OR 0= IF I . THEN
LOOP
;
> Nothing wrong with other solutions but a simple solution avoiding
> nested ifs etc uses bit vectors. The constants can be stuck in line
>
> hex
> 1249 constant 3vec
> 0421 constant 5vec
> 4000 constant probe
> 3vec 5vec or constant 3or5vec
> decimal
>
> : bang
> cr 0 101 1
> do
> ?dup 0= if probe then
> 3or5vec over and 0= if i . then
> 3vec over and if ." fizz" then
> 5vec over and if ." buzz" then
> 1 rshift cr
> loop
> drop
> ;
Very nice! I think I can make that solution more readable, though less
efficient.
variable 5mod
variable 3mod
\ repeated test3 will count to 3 and then start over at 0. Leaves a
true flag every 3rd time.
: test3 ( -- flag )
3mod @ 1+ dup 3 = if drop 0 then dup 3mod ! 0= ;
: test5 ( -- flag )
5mod @ 1+ dup 5 = if drop 0 then dup 5mod ! 0= ;
: bang
0 3mod ! 0 5mod !
101 1 do
cr
test3 dup if ." Fizz" then 0=
test5 dup if ." Buzz" then 0=
and if i . then
loop ;
We could get rid of the if in the tests.
: test3 ( -- flag )
3mod @ 1+ dup 3 = 3 and xor dup 3mod ! 0= ;
We could get rid of the variables.
: test3 ( n -- n' flag )
1+ dup 3 = 3 and xor dup 0= ;
We could factor the test to get rid of the constants.
: testN ( n limit -- n' limit flag )
>r 1+ dup r@ = r@ and xor r> over 0= ;
But it isn't getting more readable.
We can write for size and speed -- for efficiency. But an optimising
compiler might likely do it better. It isn't clear what to optimise,
either. On some processors MOD is slow. On others it isnt. With an
onchip data stack fetching variables might be very slow. Or maybe not.
Branches might be slow, but maybe not a few extra calls worth. It
makes no sense to try to optimise portable code beyond the obvious
things like taking unneeded calculations out of loops.
We can write for simplicity, to make the whole thing simpler which
reduces debugging effort and maybe decreases maintenance costs, and
the simpler code may be easier to find algorithmic improvements etc.
But simplicity is an esthetic judgement. What looks simple to one
person may look complex to another. My preference is to make it look
simpler to *me* since if the code ever does get maintained by someone
else I can't predict who'll do it. So I might as well get the benefits
now.
Writing to be easy for other people to read, it's better to avoid
subtle ideas. Anything that people find hard to understand before the
first cup of coffee is a problem.
This time around I'd say that all the 1-minute solutions are good, and
the things I put more time into come out worse.
That's why I liked John Passiniti's factoring ... it was easy enough
for me to read to directly add the pretty printing into the main loop
when I didn't like the way it printed ... it only took two iterations
to get it exactly right, which would be a record for me ... and then
that could come directly out as a word rather than being handled in
multiple locations.
The test of whether something is easy for other people to read
is other people trying to add a capability.
I'm not disagreeing with you but would just point out that it's
trivial to add pretty printing to other versions.
hex
1249 constant 3vec
0421 constant 5vec
4000 constant probe
3vec 5vec or constant 3or5vec
decimal
: bang
cr 0 101 1
do
?dup 0= if probe then
3or5vec over and 0= if i 2 .r then
3vec over and if ." fizz" then
5vec over and if ." buzz" then
dup 1 = if cr else space then
1 rshift
loop
drop
;
You wouldn't get the job, because you missed 100.
My first try was this:
: fizzbuzz
101 1 do
i 3 mod 0 = dup if ." Fizz" then
i 5 mod 0 = dup if ." Buzz" then
invert swap invert and if i . then
cr
loop ;
A bit like John's solution (is ENDIF ANS Forth?), but I have still to learn
more Forth.
--
Frank Buss, f...@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
The fact that you can add it to yours factored out as a single
word (I'll take your word for it that it factors out) is nowhere
near the same stress test as the fact that I can add it factored
out as a single word to John's.
But, yes, right aligned is even prettier
: fizz? ( n -- fl ) 3 MOD 0= DUP IF ." Fizz" THEN ;
: buzz? ( n -- fl ) 5 MOD 0= DUP IF ." Buzz" THEN ;
: ?.number ( n fl1 fl2 -- fl1 fl2 )
2DUP 2>R OR 0= IF 2 .R THEN 2R> ;
: .FizzBuzz-WS ( fl1 fl2 -- ) AND IF CR ELSE SPACE THEN ;
: .FizzBuzz-item ( n -- ) DUP fizz? OVER buzz? ?.number .FizzBuzz-WS ;
: bang1 ( -- ) 101 1 DO I .FizzBuzz-item LOOP ;
CR CR .( bang1 ) CR bang1 CR CR
My first solution (i.e. 1-minute solution) was this:
: fizz? ( n -- flag )
3 mod 0= ;
: buzz? ( n -- flag )
5 mod 0= ;
0 value notfound
: FizzBuzz
101 1 DO cr true to notfound
i fizz? IF ." Fizz" false to notfound THEN
i buzz? IF ." Buzz" false to notfound THEN
notfound IF i . THEN
LOOP ;
Interesting that we both chose exactly the same names to factor out.
I knew that a local variable would be better than a global, but I was
thinking in terms of something that might be viewed by many non-Forth
programmers. But I also had a nagging feeling that something was not
quite right. When I saw your solution it answered that question as
you had the better, no variable at all, solution. Nice.
-Doug
> You wouldn't get the job, because you missed 100.
When you removed my "I'm assuming a half-open interval" comment, were
you being deliberately deceitful?
Andrew.
> Nothing wrong with other solutions but a simple solution avoiding
> nested ifs etc uses bit vectors. The constants can be stuck in line
>
> hex
> 1249 constant 3vec
> 0421 constant 5vec
> 4000 constant probe
> 3vec 5vec or constant 3or5vec
> decimal
>
> : bang
> cr 0 101 1
> do
> ?dup 0= if probe then
> 3or5vec over and 0= if i . then
> 3vec over and if ." fizz" then
> 5vec over and if ." buzz" then
> 1 rshift cr
> loop
> drop
> ;
....
> As the perpetrator of this solution I'm very interested to know if it
> is generally considered good or bad, be as rude as you like I'm not
> the suicidal type. I'm rather surprised that you found it hard to read
> - I suppose a 2 line comment describing the method would have helped.
Yes, I think a 2 line comment would have done wonders.
I looked at it and saw some magic numbers. What are they for? Then I
looked at what you were doing with them, and it appeared you were
taking the I value and doing OVER AND 0= and getting correct results.
How could you do that? Did you have some advanced mathematical method
I'd never heard of? I tried to imagine how it could work and had no
idea. So before I did anything else I copied the code to a Forth and
tried it and verified that yes indeed, it did work.
Then I held my breath and decided that no matter how arcane it was, I
could understand it, all I had to do was study it. I looked closer at
the magic numbers. When I switched from hex to binary in my head I
noticed that 3vec had every third bit set. Ah! And then 5vec had every
fifth bit set. Then it was obvious except for the details. probe was
set to #4000, it needed to be reset precisely when both cycles were
complete, so bit 14 was the first digit that would work, do it exactly
15 times and reset. If the numbers had been 5 and 7 it would have been
harder to do.
There's nothing wrong with this. On a system where division is slow it
will run fast. On a system where nesting is slow and division is fast,
it will run slow. It isn't a method that's obvious with no comments
before the first cup of coffee. I was slow to see it partly because I
was ready to believe it might be something arcane I'd never seen
before at all, that I might have trouble with.
If we're going to judge readability, compare it with Andrew Haley's
solution.
: bang
100 1 do
i 15 mod 0= if ." FizzBuzz " else
i 3 mod 0= if ." Fizz " else
i 5 mod 0= if ." Buzz " else
i . then then then
loop ;
This solution has absolutely nothing unexpected. First case. Second
case. Third case. Fourth case. Precisely one of the four paths will be
taken. No trickiness about executing the Buzz path after the Fizz path
has already gone through. Nothing tricky anywhere. The code is bigger
than other good solutions. It will take longer to run than some --
more than half the time you do 3 mods and 3 elses. But there's nothing
here that can be misunderstood.
Readability isn't the only issue. Speaking for myself, I like to see
elegant solutions and new ideas. But the most-readable code won't have
anything new and it won't have anything that's shorter than
unexpected. Ideally it will appear to do exactly what the specs call
for, one requirement matching up with one stretch of code. So you go
down the list and see yes it did this, yes it did that, until you get
to the end. The only way Andrew's code misses this (apart from the
idea shared by everybody except computer programmers that when you say
"one to a hundred" it means start from 1 and continue until you've
done 100) is that the divisible-by-both case comes out of order.
> If we're going to judge readability, compare it with Andrew Haley's
> solution.
> : bang
> 100 1 do
> i 15 mod 0= if ." FizzBuzz " else
> i 3 mod 0= if ." Fizz " else
> i 5 mod 0= if ." Buzz " else
> i . then then then
> loop ;
> This solution has absolutely nothing unexpected. First case. Second
> case. Third case. Fourth case. Precisely one of the four paths will be
> taken. No trickiness about executing the Buzz path after the Fizz path
> has already gone through. Nothing tricky anywhere. The code is bigger
> than other good solutions.
No it isn't. AFAIK it's the smallest, both in the size of the source
and the object. John Passaniti's gets close, but it's 584 bytes long
whereas mine was 576. [AMD64 gforth.] Not that I was trying for small
size -- that was just a side-effect of a simple solution.
Andrew.
> > This solution has absolutely nothing unexpected. First case. Second
> > case. Third case. Fourth case. Precisely one of the four paths will be
> > taken. No trickiness about executing the Buzz path after the Fizz path
> > has already gone through. Nothing tricky anywhere. The code is bigger
> > than other good solutions.
>
> No it isn't. AFAIK it's the smallest, both in the size of the source
> and the object. John Passaniti's gets close, but it's 584 bytes long
> whereas mine was 576. [AMD64 gforth.] Not that I was trying for small
> size -- that was just a side-effect of a simple solution.
I'm surprised. It has redundant code, I'd expect it to be larger.
John's code has surplus definitions and surplus calls due to his
factoring. If the headers count that will push up his size, if you
only count code then it's just a couple of extra calls.
With gForth 0.6.2
unused : bang
100 1 do
i 15 mod 0= if ." FizzBuzz " else
i 3 mod 0= if ." Fizz " else
i 5 mod 0= if ." Buzz " else
i . then then then
loop ; unused - .
got 304.
unused : bang
101 1 do
cr
i 3 mod 0= dup if ." Fizz" then
i 5 mod 0= dup if ." Buzz" then
or 0= if i . then
loop ; unused - .
gave 228.
: bang
101 1 do
cr
i 5 mod 0=
i 3 mod 0=
2dup or 0= if i . then
if ." Fizz" then
if ." Buzz" then
loop ;
gave 224 because it uses 2DUP in place of DUP DUP . It's harder to
read.
>> > This solution has absolutely nothing unexpected. First case. Second
>> > case. Third case. Fourth case. Precisely one of the four paths will be
>> > taken. No trickiness about executing the Buzz path after the Fizz path
>> > has already gone through. Nothing tricky anywhere. The code is bigger
>> > than other good solutions.
>>
>> No it isn't. AFAIK it's the smallest, both in the size of the source
>> and the object. John Passaniti's gets close, but it's 584 bytes long
>> whereas mine was 576. [AMD64 gforth.] Not that I was trying for small
>> size -- that was just a side-effect of a simple solution.
> I'm surprised. It has redundant code, I'd expect it to be larger.
> John's code has surplus definitions and surplus calls due to his
> factoring. If the headers count
Well, yeah. Of course they count! I mean, someone had to pay for
that RAM they're in...
> that will push up his size, if you only count code then it's just a
> couple of extra calls.
Andrew.
The point of FizzBuzz is that it is part of a coding skill test given to
job candidates. The author's claim is that many job candidates can't
code, and tests like FizzBuzz are used to weed such weaker candidates out.
My response in comp.lang.forth was simply to provide the original poster
an example of what he wanted-- how would the solution to FizzBuzz look
in Forth. One assumes that the original poster understood that there
are endless variations on a theme. One assumes the original poster
understands that one can optimize for different things-- size, speed,
clarity, programmer effort, generality, reusability, etc.
> John's code has surplus definitions and surplus calls due to his
> factoring. If the headers count that will push up his size, if you
> only count code then it's just a couple of extra calls.
If the problem statement for FizzBuzz had said, "minimize code size" or
"generalize your code so that other multiples can print out different
messages" then the choices I made would have been different. This is
why context matters. In a job interview, I would have taken less than a
minute to code my solution and would have been able to move on to other
problems. Those of you who are agonizing over if an additional term
could be factored out or are counting the number of bytes generated are
the ones who might have less time for the more substantive questions
during the interview.
Sorry I caused you some grief with the code. I must make an effort
with making my code more readable, after all its far less effort for 1
writer to do that compared to n readers decoding something.
Gerry
Your solution, in addition to excellent clarity and simplicity, IMO,
is also easily extensible. Consider the case where the problem is
expanded to include Boom (divisible by 7) and Zapp (divisible by 9):
: boom? ( n -- flag ) \ 0 if hit
7 mod 0= dup IF ." Boom" THEN ;
: zapp? ( n -- flag ) \ 0 if hit
9 mod 0= dup IF ." Zapp" THEN ;
: FizzBuzzBoomZapp
101 1 DO cr
i fizz?
i buzz? or
i boom? or
i zapp? or
0= IF i . THEN
LOOP ;
This is a reasonable variation of the original problem.
So 42 would yield FizzBoom and 45 would yield FizzBuzzZapp and so
forth. Two of your original definitions were re-usable.
-Doug
I didn't at all intend to criticise your code. I think it is
excellent. The factoring is not a bad thing -- though I think the code
is a little more readable without it, and the factored definitions
that are used only once increase the code size a little. That isn't
any big deal, and in the context you were responding to -- a job
interview for a Forth job -- would show that you have the factoring
concept firmly in mind.
The lesson I got from this is that factoring into small short pieces
can impede readability, and that careful naming can reduce that
problem.
The lesson I got from my own experiments was that anything tricky
reduces readability and should be used only when there are clear
advantages that override that disadvantage.
Again, I meant no criticism. I thought that every solution except mine
was excellent. Gerry's solution had the disadvantage of not being what
a beginning Forth programmer would expect, which made it harder to
read. But in some systems his tests would be far faster. Of course,
more than half the time you're doing up to 3 mods or dmods to generate
a numeric string to . so it won't be that big a percentage
improvement.
Once again, as you point out, there was nothing about minimising code
size in the specs and there's no shame in not having the smallest code
when compiled on some particular system. I have no complaint about
your 1-minute code. I don't think it should be the standard for
judging code size since it's large, and that says nothing bad about
you or your code.
> Sorry I caused you some grief with the code. I must make an effort
> with making my code more readable, after all its far less effort for 1
> writer to do that compared to n readers decoding something.
It's no big deal. I wanted to note that code is more readable when it
does nothing unexpected. Your innovative solution was fundamentally
harder to read because it was innovative. Writing carefully to expose
your methods, along with careful documentation, could palliate that.
But anything that your readers don't expect is harder to read than
what they do expect, and there's no getting around that.
: zap dup 0 .r ; : fizz ." Fizz" ; : buzz ." Buzz" ; : fizzbuzz fizz
buzz ;
create foo ' zap , ' fizz , ' buzz , ' fizzbuzz ,
:this foo does> >r dup 5 mod 0= 2* over 3 mod 0= + cells r> + @c
execute drop ;
: bar 101 1 do i foo space loop ; bar
Hans Bezemer
Ah, but a benefit of open-source is that the effort by
the one writer does not have to always be the code author.
Applying the rule, "explain magic numbers", I reckon
it gets to between first and second cup of coffee
stage for me, which for reasonably handy programmers
would probably push it back before the first cup.
hex
1249 constant 3vec \ = 0001001001001001, each 3rd bit set
0421 constant 5vec \ = 0000010000100001, each 5th bit set
4000 constant probe \ = 0100000000000000, edge of frame
I prefer
binary
0001001001001001 constant 3vec \ each 3rd bit set
0000010000100001 constant 5vec \ 5th bit set
0100000000000000 constant probe \ edge of frame
3vec 5vec or constant 3or5vec
decimal
When a particular BASE is native to the problem, use it.
Jerry
--
Engineering is the art of making what you want from things you can get.
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
> I prefer
> binary
> 0001001001001001 constant 3vec \ each 3rd bit set
> 0000010000100001 constant 5vec \ 5th bit set
> 0100000000000000 constant probe \ edge of frame
> 3vec 5vec or constant 3or5vec
> decimal
Meaning that the commented above is better than the
uncommented, and the base that conveys the information
directly is even better?
I have no problem with that ranking at all.
numbers conv
: n DUP . 1+ ;
: f ." Fizz " 1+ ;
: b ." Buzz " 1+ ;
: fb ." FizzBuzz " 1+ ;
: y n n f n b f n n f b ;
: x y n f n n fb ;
: go 1 x x x x x x y DROP ;
Well, sometimes brute force will work. But if the problem description
changes then that approach often isn't helpful. For example, try
that for solving the FizzBuzzBoomZapp problem for say 1 through 1000.
-Doug
Ah, but you only have to find the LCD of the factors and
implement it for the LCD, with the numbers adding a value
passed to them on the stack and passing that value on
unmodified, then find the Modulus of the loop limit and
do a simple FOR loop, adding the LCD each time through
the loop, followed by enough of the LCD sequence to get
through to the loop terminus.
Of course, implementing the LCD and Modulus routines
are left as an exercise for the reader.
Or you could just use:
: FizzBuzzBoomZapp
1001 1 DO cr
i fizz?
i buzz? or
i boom? or
i zapp? or
0= IF i . THEN
LOOP ;
Q.E.D.
-Doug
Which way is brute force? One way you do the calculations once and
never again. The other way you repeat them every time.
However, if the critical values are 3 5 7 11 then the least common
multiple is 1155 . You aren't going to have a lot of regular repeating
patterns in that. Better to use bit vectors or simulated bit vectors
than try to code it all into the definition.
I find your way far more readable. Anything innovative is harder to
read because the reader has to think.
It also took less than 30 seconds to code and worked first time
through.
Anything innovative is harder to
> read because the reader has to think.
What is the goal? Solving a problem or being "innovative"? One could
argue that John P's approach is indeed innovative, if that is deemed
important here.
-Doug
> What is the goal? Solving a problem or being "innovative"?
> One could argue that John P's approach is indeed innovative,
> if that is deemed important here.
I prefer:
: bang3 ( -- ) 1001 1 DO i .FizzBuzzBoomZapp LOOP ;
John's approach translates directly to
: .FizzBuzzBoomZapp ( n -- )
DUP >R fizz?
R@ buzz? or
R@ boom? or
R@ zapp? or
0= IF i . THEN
RDROP ;
: bang3 ( -- ) 1001 1 DO i .FizzBuzzBoomZapp LOOP ;
Of course, it only makes a difference when it is
a real problem with a real world side-effect
as the primary target, and it comes time to
determine why the loop is not working correctly.
At that point, you'll want to do that factoring
between the loop and the loop action if you haven't
done so already.
As far as readability, if you are in the habit
of reading from back to front, the longer the
individual definition the more annoying.
We have our Obfuscated Forth Contest winner.
> Your solution, in addition to excellent clarity and simplicity, IMO,
> is also easily extensible. Consider the case where the problem is
> expanded to include Boom (divisible by 7) and Zapp (divisible by 9):
>
> : boom? ( n -- flag ) \ 0 if hit
> 7 mod 0= dup IF ." Boom" THEN ;
>
> : zapp? ( n -- flag ) \ 0 if hit
> 9 mod 0= dup IF ." Zapp" THEN ;
>
> : FizzBuzzBoomZapp
> 101 1 DO cr
> i fizz?
> i buzz? or
> i boom? or
> i zapp? or
> 0= IF i . THEN
> LOOP ;
>
> This is a reasonable variation of the original problem.
> So 42 would yield FizzBoom and 45 would yield FizzBuzzZapp and so
> forth. Two of your original definitions were re-usable.
>
> -Doug
Exercise: use CREATE-DOES> to remove the duplication between the *?
words.
Ian
: fizzbuzzoff ." I don't care for gimmicks. Good day" ;
When I interview a candidate, I prefer to pose real challenges that
touch on experience. No one interviewing an engineer asks them to
build a birdhouse.
I've worked with engineers who were terrible programmers. I've also
worked with engineers who simply didn't have a lot of experience
programming. Being a great engineer says nothing about your ability to
express the solution to a problem in code. The point of FizzBuzz-style
questions is to weed these candidates out (or to better identify their
strengths if they are hired).
FizzBuzz-style questions are also great if they have an element of
ambiguity to them. This forces the candidate to not just dive into the
problem, but to interact with you and resolve any questions and clarify
requirements. And quite often I've found, you learn the most about a
candidate not by the actual work on solving the FizzBuzz-style question,
but prior to it-- during the part when there is a back-and-forth in
better defining the problem. Are they asking intelligent questions?
Are they over-analyzing the problem? Are they asking questions because
they suffer from "specification paralysis."
> No one interviewing an engineer asks them to
> build a birdhouse.
Your refusal to do a FizzBuzz-style question because it doesn't pose a
"real challenge" would itself tell me something about your character and
likely experience. It might also say something about your respect for
others.
In terms of character, it might indicate you are a prima donna engineer
who likely wouldn't fit in with our company's culture. We don't believe
that any of us are so special that we are "above" any kind of work.
In terms of experience, your refusal to do a FizzBuzz-style question
might indicate a lack of real-world experience. Because in the real
world, although it sure would be nice if you could always be presented
with the most challenging work possible, sometimes you have to get your
hands dirty and just sling out some low-level code to solve an immediate
problem.
In terms of respect, you are applying for a job and you likely aren't
the only one. Most companies (like the one I work for) don't have
infinite resources, so any new hires represent a serious investment by
the company in you. The company conducts a job interview not to amuse
you for an afternoon, but to tell if an investment in you is worthwhile.
So we will ask you FizzBuzz-style questions as well as higher-level
questions to determine if you're the best candidate for the job.
Don't want to take the test? That's nice. Don't let the door slam you
on the ass on your way out.
> John's approach translates directly to
>
> : .FizzBuzzBoomZapp ( n -- )
> DUP >R fizz?
> R@ buzz? or
> R@ boom? or
> R@ zapp? or
> 0= IF i . THEN
> RDROP ;
>
> : bang3 ( -- ) 1001 1 DO i .FizzBuzzBoomZapp LOOP ;
>
> Of course, it only makes a difference when it is
> a real problem with a real world side-effect
> as the primary target, and it comes time to
> determine why the loop is not working correctly.
> At that point, you'll want to do that factoring
> between the loop and the loop action if you haven't
> done so already.
Yes. I agree that is a reasonable factoring. As you say, it allows
for easy testing of the inner engine, divorced from the loop.
-Doug
Here's a solution (not the simplest) using Reva (note there are
distinctly non ANS words here):
create fizzbuzz " FizzBuzz" here,
variable fizzed
: fizzbuzz? ( i a mod -- i )
2 pick swap mod
0if 4 type fizzed on ;then drop ;
: num? ( i -- ) fizzed @ if space drop ;then . ;
: fizz? ( i -- i ) fizzbuzz 3 fizzbuzz? ;
: buzz? ( i -- i ) fizzbuzz cell+ 5 fizzbuzz? ;
: fb 101 1 do fizzed off i fizz? buzz? num? loop ;
I tried to avoid using conditionals and preferred to factor pretty
heavily. It depends on 4 byte cells, which Reva uses. It also adds a
space after the output word, for cleaner looking output.
> : bang3 ( -- ) 1001 1 DO i .FizzBuzzBoomZapp LOOP ;
That'll teach me to send code from a computer without
a Forth installed:
: .FizzBuzzBoomZapp ( n -- )
DUP >R fizz?
R@ buzz? or
R@ boom? or
R@ zapp? or
0= IF R@ . THEN
RDROP ;
The last, conditional, I is, after all, which its
an RDROP at the end instead of a final R> ... and
then I didn't replace it.
Is it obfuscated? I think it's the ColorForth entry, because it uses the
most simple basics (apart from ." and .), and doesn't loop.
--
Bernd Paysan
"If you want it done right, you have to do it yourself"
http://www.jwdt.com/~paysan/
> : fizzbuzzoff ." I don't care for gimmicks. Good day" ;
> When I interview a candidate, I prefer to pose real challenges that
> touch on experience.
Sure, but these microproblems can be very revealing. Some apparently
fantastically knowledgeable people can fail, and that tells you
something. Also, having written code in an interview such a
microproblem, I was once asked what code the compiler I used would
generate for it. That, I tell you, really separates the sheep from
the goats.
Andrew.
Once again I see words. But no results. Where is the better
definition?
I answer technical questions just fine, and I ask them as well when
interviewing candidates. But of course, I always run into people
online who talk about who they'd "fire on the spot", or who they
wouldn't consider hiring based on some narrow technical case, and I'm
always curious as to whether they've ever actually been in that
position, or if it's just another case of "If I Ran the Zoo", or the
phenomenom where everyone on the internet is an amateur lawyer.
Consider for a moment that my answer was hyperbole. Consider what an
awesome waste of time it was to dress me down in such voluminous
prose. Maybe it's just that this microproblem was just so durned
silly... but I still stand by my aspersion.
chuck
> > Which way is brute force? One way you do the calculations once and
> > never again. The other way you repeat them every time.
>
> > However, if the critical values are 3 5 7 11 then the least common
> > multiple is 1155 . You aren't going to have a lot of regular repeating
> > patterns in that. Better to use bit vectors or simulated bit vectors
> > than try to code it all into the definition.
>
> Once again I see words. But no results. Where is the better
> definition?
It depends on your needs which is better.
The way you implied was brute force did an absolute minimum of
calculation at runtime. It had the loop unrolled, but factored. very
elegant, though you had to understand what it was doing to read the
code.
The other extreme did three divisions every time. It seemed to come
natural to a lot of people.
The intermediate did three table lookups every time, and it had things
arranged so the tables were bit-vectors and were easy to access. No
divisions, ever, except for those inherent in printing out numbers.
(Do it in hex and you can avoid those too.) Slower than the loop-
unrolling method, simpler than the brute force method, but again you
have to understand how it works to read the code.
I pointed out that the advantages of loop-unrolling for this problem
evaporate when the problem changes with more prime numbers and higher
N. Cycle length 15 is easy. Cycle length 1155 is not so easy. You
could find shorter cycles that do get repeated and alternate them, and
the code will be even less readable.
But the table lookups work fine then. You can use 4 tables for 4
variables, and OR the results.
> Once again I see words. But no results. Where is the better
> definition?
Was I unclear? Which is best depends on your goals. The loop-unrolling
method is the fastest. The bit-vector method is the most elegant. The
brute force method is the most readable.
[big snip]
> Was I unclear?
Yes.
> Which is best depends on your goals. The loop-unrolling
> method is the fastest. The bit-vector method is the most elegant. The
> brute force method is the most readable.
Let's just solve the problem. My programming time is valuable. Last
I heard computers are pretty good at executing code. Where is the
better (elegant? maintainable? whatever?) solution?
Now you are unclear. Tell us what "good" means to you and then you can
tell us what solution you like best.
Let's just solve the problem. Where is your solution (based on
elegance, maintainabillty, whatever, you pick)? I've already given my
solution.
I like most of the solutions that have been presented. But if you want
the ultimate of maintainability, something like this is likely to be
good:
10 FOR I = 1 TO 100
20 IF (I MOD 3 = 0) THEN PRINT "Fizz";
30 IF (I MOD 5 = 0) THEN PRINT "Buzz";
40 IF ((I MOD 3 <> 0) AND (I MOD 5 <> 0)) THEN PRINT I;
50 PRINT
60 NEXT I
I don't really think that BASIC is easier to learn than Forth. But a
lot of people think it is, and a lot of people learn it very quickly
-- maybe partly because their confidence is so high. Practically
anybody can quickly learn enough BASIC to maintain this program.
It just doesn't get much more readable and maintainable than this.
Your time is valuable. The time of a high-school graduate who can
maintain a program like this is not. This program is the cheapest to
maintain.
> I like most of the solutions that have been presented. But if you want
> the ultimate of maintainability, something like this is likely to be
> good:
>
> 10 FOR I = 1 TO 100
> 20 IF (I MOD 3 = 0) THEN PRINT "Fizz";
> 30 IF (I MOD 5 = 0) THEN PRINT "Buzz";
> 40 IF ((I MOD 3 <> 0) AND (I MOD 5 <> 0)) THEN PRINT I;
> 50 PRINT
> 60 NEXT I
>
> I don't really think that BASIC is easier to learn than Forth. But a
> lot of people think it is, and a lot of people learn it very quickly
> -- maybe partly because their confidence is so high. Practically
> anybody can quickly learn enough BASIC to maintain this program.
You didn't solve the problem. The problem was clearly defined as
FizzBuzzBoomZapp. From 1 to 1000. Read up a few posts if you didn't
catch that. (Hint: It might help to use Forth. I'm fairly certain
this is a Forth newsgroup.)
(Hope this isn't a dupe. Waited a day and didn't see my reply...)
Yes, it is a color forth or machine forth inspired solution.
But there is nothing particularly machine or color forth
specific. It really is just basic forth. If the toy names for
the toy problem don't suit you, here is a version with long
names and very slightly refactored. I find it harder to read
than the original.
: show.number DUP . 1+ ;
: show.fizz ." Fizz " 1+ ;
: show.buzz ." Buzz " 1+ ;
: show.fizz.buzz ." FizzBuzz " 1+
;
: show.1.5 show.number show.number show.fizz
show.number show.buzz
;
: show.6.10 show.fizz show.number show.number
show.fizz show.buzz
;
: show.11.15 show.number show.fizz show.number
show.number show.fizz.buzz
;
: show.10 show.1.5 show.6.10 ;
: show.15 show.10 show 11.15 ;
: show.30 show.15 show.15 ;
: show.90 show.30 show.30 show.30 ;
: go 1 show.90 show.10 DROP ;
Now the following code is much more fun! I have tried to solve
the Fizz Buzz Boom Zap problem with a machine forth like forth:
no "-", "/", "*", "mod", or any kind of loop; but with multiple
exit points, recursion, and tail recursion elimination. Since
it has to produce output, I will assume the usual ., .", and
type. Sadly I don't have a colorForth or machine forth that I
can use to debug the code. Worse still, my forth foo isn't good
enough to teach my forth about the needed machine forth
behavior.
I will use the A register operations. They are primitives in
various machine forths, here are working def's for other forths.
variable A
: A@ A @ ;
: A! A ! ;
: @A A@ @ ;
: !A+ A@ ! A@ cell + A! ;
( Here are some challenge problems. Define these words so they
act like the machine forth versions. )
( "XIF" acts like "dup if" and "XTHEN" like "then" )
: XIF ; immediate
: XTHEN ; immediate
( Acts like ":" but needs to allow recursion. )
: X: ; immediate
( Something like ";" This is probably a tough exercise.
Remember, it should translate tail calls to jumps -- tail
recursion elimination at the least. The easy way out is just a
jump to next. )
: X; ; immediate
( The pair X: ;X have to somehow manage compile vs. interpret.
Colored words might help. )
( --------- Now the real code --------- )
X: 1- -1 + X; X: 1+ 1 + X;
X: mod1+ ( mod n -- n+1 )
( only works for n such that 0 <= n < mod )
1+ swap over xor XIF drop X; XTHEN swap drop X;
X: p.name? ( f name mod -- f )
@A mod1+ dup !A+ ( f name new.residue ;
m[A] = new.residue )
XIF drop drop X; XTHEN ( f )
drop type drop 0 X; ( 0 )
X: Fizz? " Fizz" 3 p.name? X;
X: Buzz? " Buzz" 5 p.name? X;
X: Boom? " Boom" 7 p.name? X; ( or 9 )
X: Zap? " Zap" 11 p.name? X;
X: Number? ( f -- f ; m[A] incremented )
@A 1+ !A XIF @A . X; XTHEN ." " X;
( a: how exactly does color forth do this? b: how would you do
it without colors? The "2 2 +" is just to emphasize at the
problem. )
variable fbbz.residues 2 2 + allot
X: go1 ( -- )
fbbz.residues A!
1 Fizz? Buzz? Boom? Zap? Number? drop X;
X: go.loop go1 1- XIF go.loop X; XTHEN X;
X: go fbbz.residues A!
0 !A+ 0 !A+ 0 !A+ 0 !A+ 0 !A+
1000 go.loop X;
Have I done something that offended you? I think I'm detecting a
certain coolness to your tone. I remember years when this newsgroup
was nice and mellow, experts and hobbyists and newbies got along real
well. And only a few years ago the only real irritants were Jeff Fox
and John Passaniti. Oh well.
I like your solution to the 4 variable problem. If there's any
improvement to be made it would be factoring your 4 subroutines, which
repeat the exact same code 4 times and change only the data.
Here's one way:
: makesub ( n -- ) ( E: i -- 0|n )
CREATE C, PARSE-NAME STRING,
DOES> TUCK C@ MOD TUCK 0= IF 1+ COUNT TYPE 0 THEN DROP ;
3 makesub fizz? Fizz
5 makesub buzz? Buzz
7 makesub boom? Boom
9 makesub zapp? Zapp
I find this code harder to read, but I'm guessing it might be slightly
more maintainable since instead of copying and pasting the definition
you could just do
11 makesub zilch? Zilch
etc.
On the other hand, if you're going to have a whole series of them, why
name them? You could put the addresses of the data structures into an
array, and loop through the array. But this is overkill.
I think this is somewhat less readable. But the code should be
smaller. We re-use the same code 4 times. When should we sacrifice
readability for size? On memory-limited systems, one of Forth's
traditional niches?
Now, the bit-array approach is very nice but it depends on 3 5 *
fitting into a cell, and 3 5 * 7 * does not fit except in a few
systems. We can do without the double, triple, and quadruple bit-
arrays. I'll use a similar method that I think is more readable.
in place of .... 3 MOD DUP 0= IF ." Fizz" THEN
we can use
.... 1 fizzle DUP 2@ = IF ! ." Fizz" FALSE EXIT THEN +! TRUE ....
We count to three, when we get there we reset the counter.
One advantage of this approach is that it can be maintained by
somebody who isn't real clear what MOD does. And it's faster when MOD
is slow compared to this long expression.
Anyway, I liked your solution. And if you want readability, nothing
can beat the BASIC version which I won't bother to modify for you.
Earlier you wrote:
> Which way is brute force? One way you do the
> calculations once and never again. The other
> way you repeat them every time.
> However, if the critical values are 3 5 7 11
> then the least common multiple is 1155 .
> You aren't going to have a lot of regular repeating
> patterns in that. Better to use bit vectors or
> simulated bit vectors than try to code it all into
> the definition.
I wanted to see this "better" definition. Its existence was as I
suspected.
> > Have I done something that offended you? I think I'm detecting a
> > certain coolness to your tone.
> Earlier you wrote:
> > Which way is brute force? One way you do the
> > calculations once and never again. The other
> > way you repeat them every time.
> > However, if the critical values are 3 5 7 11
> > then the least common multiple is 1155 .
> > You aren't going to have a lot of regular repeating
> > patterns in that. Better to use bit vectors or
> > simulated bit vectors than try to code it all into
> > the definition.
>
> I wanted to see this "better" definition. Its existence was as I
> suspected.
?? Did it sound like I was saying the simulated bit vectors would be
better than your solution?
I intended to say that the loop-unrolling method would be far worse on
the larger problem, and the simulated bit-vectors would be better than
that.
For the simple problem the loop unrolling method depended on the
solution cycling every 15 times. So they could code the 15-cycle, and
then repeat that 6 times,and then do the first 10 again to get to 100.
Getting to 1000 scales easily, do the cycle 66 times and then the
first ten. But with 3 5 7 9 the cycle is 315 and unrolling the loop
by hand takes a lot of thinking. I guess you could find repetitions
within the 315 length and factor those.
So as the problem gets more complex the brute force method gets
relatively easier. Just tell the computer to work out all the details
for itself, every time.
Ah! By "code it all into the definition" I mean "unroll the loop so
you make no decisions at runtime". If it sounded like I was talking
about your solution, I apologise. I like your solution.
Hi Ron,
> It depends on 4 byte cells, which Reva uses.
get that fixed if you ever want a 64bit version ;)
I'll introduce to other "alien" syntax solutions for 4p/HelFORTH
(tested with 4p, but they should work without much modification in
HelFORTH):
---------------------
: y vector dup . ;
: x"` ."` then> `space is y ;
: g <\ \\ \\ x" Fizz" \>
<\ \\ \\ \\ \\ x" Buzz" \>
y undo y 1+ ;
1 100 times g drop
---------------------
This first version uses "coroutines" as introduced with HelFORTH. I'll
explain it step by step.
1) The definition of "y" uses the RetroForth inspired kind of defered
words that do have a default behaviour. "vector" means simply to
compile a branch that by default goes to the next instruction. "is"
can change this to whatever target you want and "undo" resets this
jump to next instruction.
2) The definition of x" uses features borrowed by FreeForth. The `
(backtick) behind
: x"`
means that x" will be an immediate word. The backtick behind
."`
means to use the macro ." at execution time. The "then>" you can
imagine as ANS (I hope I make no error in it):
: THEN> STATE @ IF R> COMPILE EXIT THEN ; IMMEDIATE
The backtick in front of "space" is similar to:
['] space
3) Inside the definition of "g" there is two times used a feature
called "coroutines". They are anonymous, so you can only break a
couroutine and execution is continued next time at the code that
follows. The coroutines start with "<\" and and end with "\>". The
points where a coroutine is interrupted are marked with "\\". "\>"
closes the circle - so if the code reaches "\>" next time the
coroutine continues directly after "<\".
Alien?
The second thing, also for 4p and I think a little more traditional:
---------------------
: x dup 5 mod if
: y vector dup .
;then ." Buzz " ;
: z dup 3 mod or: ." Fizz" `space is y ;
: g z x undo y 1+ ;
1 100 times g drop
---------------------
1) "x" falls thrue into "y".
2) ";then" would be similar to the sequence "EXIT THEN"
3) "z" uses "or:" this is similar to
: OR: IF RDROP THEN ;
I do think that "times" or "TIMES" is commonly known as a way to
shorten some programs, so I dont explain it further.
Have Fun!
-Helmar
PS: http://maschenwerk.de/HelFORTH/ is the starting point for HelFORTH
and 4p.
It depends how you do the loop unrolling. Accepting by a moment the
reason for needing (like a very limited platform, etc.): do you agree to
write a short piece of Forth code to generate the unrolled code for the
loop as part of solution (this probably done in a hosted environment)?
[snipped]
> So as the problem gets more complex the brute force method gets
> relatively easier. Just tell the computer to work out all the details
> for itself, every time.
And generate a 'static' solution for the bit version version!
my .019999....
> > I intended to say that the loop-unrolling method would be far worse on
> > the larger problem, and the simulated bit-vectors would be better than
> > that.
>
> It depends how you do the loop unrolling. Accepting by a moment the
> reason for needing (like a very limited platform, etc.): do you agree to
> write a short piece of Forth code to generate the unrolled code for the
> loop as part of solution (this probably done in a hosted environment)?
OK. Where there are 4 outcomes for 3 and 5, for 3 5 7 9 there are 16
outcomes.
Suppose we asrrange them in an array, and use a jump table to choose
which to use.
Then we can make the choice with a number, data. A nibble.
In the simplest case that would be 158 bytes (or maybe 315 bytes) of
data that could come from some sort of mass storage, and there might
be patterns in it to exploit.
On the host we can generate that sequence and look for patterns.
: guts
dup 3 mod 0= 1 and
over 5 mod 0= 2 and or
over 7 mod 0= 4 and or
swap 9 mod 0= 8 and or ;
: actions
316 1 do
i guts .
loop ;
actions 0 0 1 0 2 1 4 0 9 2 0 1 0 4 3 0 0 9 0 2 5 0 0 1 2 0 9 4 0 3 0
0 1 0 6 9 0 0 1 2 0 5 0 0 11 0 0 1 4 2 1 0 0 9 2 4 1 0 0 3 0 0 13 0 2
1 0 0 1 6 0 9 0 0 3 0 4 1 0 2 9 0 0 5 2 0 1 0 0 11 4 0 1 0 2 1 0 4 9 2
0 1 0 0 7 0 0 9 0 2 1 4 0 1 2 0 9 0 4 3 0 0 1 0 2 13 0 0 1 2 0 1 4 0
11 0 0 1 0 6 1 0 0 9 2 0 5 0 0 3 0 0 9 4 2 1 0 0 1 2 4 9 0 0 3 0 0 5 0
2 9 0 0 1 6 0 1 0 0 11 0 4 1 0 2 1 0 0 13 2 0 1 0 0 3 4 0 9 0 2 1 0 4
1 2 0 9 0 0 7 0 0 1 0 2 9 4 0 1 2 0 1 0 4 11 0 0 1 0 2 5 0 0 9 2 0 1 4
0 3 0 0 9 0 6 1 0 0 1 2 0 13 0 0 3 0 0 1 4 2 9 0 0 1 2 4 1 0 0 11 0 0
5 0 2 1 0 0 9 6 0 1 0 0 3 0 4 9 0 2 1 0 0 5 2 0 9 0 0 3 4 0 1 0 2 9 0
4 1 2 0 1 0 0 15 ok
Repeat this sequence 3 times, then do just the first 55 of them.
Extract a nibble, jump into a jump table, execute an xt. As compared
to just execute a command. Or use whole bytes and avoid the nibble
stuff for an extra 157 bytes.
It stands out from the sequence that apart from the last item, the
sequence is a palindrome. So we could cut the length in half if we're
willing to sample it from both ends. Would that be worth doing?
Probably not.
So we'd have something like
CREATE jumptable ' .I , ' Fizz , ' Buzz , ' FizzBuzz , ' Boom , '
FizzBoom , ' BuzzBoom , ' FizzBuzzBoom , ' Zapp , ' FizzZapp , '
BuzzZapp , ' FizzBuzzZapp , BoomZapp , ....
: action
S" our-file" R/O BIN OPEN-FILE ?FILE our-file !
1001 1 DO
i get-action CELLS jumptable + @ EXECUTE DROP
LOOP ;
And depending on how performance goes on your system you could have
something like
: get-action ( i -- i n )
DUP BEGIN DUP 315 > WHILE 315 - REPEAT
0= IF 0 0 our-file @ REPOSITION-FILE ?FILE THEN
HERE DUP 1 our-file @ READ-FILE ?FILE DROP C@ ;
Or you might rather put all the data in memory with one operation and
then cycle through it.
So the code can be reasonably simple, but not ideally readable. In
this particular case, it usually wouldn't be practical to do all this
just to avoid a few MODs. But note that the method generalises better
than the obvious approach. We can generate any sequence whatsoever
once, and then repeat it as many times as required. Even if the specs
that say what the sequence must be take many pages, even if they're
generated at random, provided the sequence is fixed we can generate it
once and then the code will use the same resources and run at the same
speed as the current version.
> > I intended to say that the loop-unrolling method would be far worse on
> > the larger problem, and the simulated bit-vectors would be better than
> > that.
>
> It depends how you do the loop unrolling. Accepting by a moment the
> reason for needing (like a very limited platform, etc.): do you agree to
> write a short piece of Forth code to generate the unrolled code for the
> loop as part of solution (this probably done in a hosted environment)?
OK. Where there are 4 outcomes for 3 and 5, for 3 5 7 9 there are 16
> > I intended to say that the loop-unrolling method would be far worse on
> > the larger problem, and the simulated bit-vectors would be better than
> > that.
>
> It depends how you do the loop unrolling. Accepting by a moment the
> reason for needing (like a very limited platform, etc.): do you agree to
> write a short piece of Forth code to generate the unrolled code for the
> loop as part of solution (this probably done in a hosted environment)?
OK. Where there are 4 outcomes for 3 and 5, for 3 5 7 9 there are 16
> > I intended to say that the loop-unrolling method would be far worse on
> > the larger problem, and the simulated bit-vectors would be better than
> > that.
>
> It depends how you do the loop unrolling. Accepting by a moment the
> reason for needing (like a very limited platform, etc.): do you agree to
> write a short piece of Forth code to generate the unrolled code for the
> loop as part of solution (this probably done in a hosted environment)?
OK. Where there are 4 outcomes for 3 and 5, for 3 5 7 9 there are 16
> So the code can be reasonably simple, but not ideally readable. In
> this particular case, it usually wouldn't be practical to do all this
> just to avoid a few MODs. But note that the method generalises better
> than the obvious approach.
I agree. I would say that if we are solving the problem on a platform
where this approach is appropriate it would not hurt to have a single
line comment directing the prospective maintainer to a more thorough
document.
> We can generate any sequence whatsoever
> once, and then repeat it as many times as required. Even if the specs
> that say what the sequence must be take many pages, even if they're
> generated at random, provided the sequence is fixed we can generate it
> once and then the code will use the same resources and run at the same
> speed as the current version.
>
Yes, you got my point nicely. Thanks for investing your time creating an
example in code!
Regards,
--
Cesar Rabak
1 : .fizz ." fizz" ; : .buzz ." buzz" ;
2
3 ( contorized.act - mem map: n C xt)
4 : n<C? ( a-f) dup @ swap cell + @ < ;
5 : act ( a-) 2 cells + @ execute ;
6 : contorized.act ( xt n --) create 0 , , ,
7 does> >R 1 R@ +! R@ n<C? if 1 else R@ act 0 R@ ! 0 then
8 R> drop ;
9 ' .fizz 3 contorized.act 3do
10 ' .buzz 5 contorized.act 5do
11 : doprint cr 101 1 do 3do 5do and if i . then cr loop ;
- snapshot from gforth block editor.
best regards,
hd