Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Fizz Buzz Zoom

320 views
Skip to first unread message

Paul Rubin

unread,
Oct 7, 2012, 12:19:35 PM10/7/12
to
This works in gforth. I'm not sure if it's valid ANS Forth, since it
requires multiple s" ..." string literals to be alive at the same time.
Coding and testing wasn't difficult in absolute terms, since the program
is so small, but it still took multiple tries and head-scratching, and
the result seems unmodular compared to a Python version. Maybe there
was something I could have done better.

: fb0 ( flag sa su n d -- flag )
mod 0= if type -1 else 2drop 0 then or ;

: fb ( n -- flag ) >r
0 ( flag )
s" Fizz" r@ 3 fb0
s" Buzz" r@ 5 fb0
s" Zoom" r> 7 fb0 ;

: f ( n -- ) dup fb ( flag ) 0= if . else drop then cr ;

: run ( -- ) \ run to 120 so that 3*5*7=105 is included
120 1 do i f loop ;

Marcel Hendrix

unread,
Oct 7, 2012, 2:33:18 PM10/7/12
to
Paul Rubin <no.e...@nospam.invalid> writes Re: Fizz Buzz Zoom
[..]
> This works in gforth. I'm not sure if it's valid ANS Forth, since it
> requires multiple s" ..." string literals to be alive at the same time.
> Coding and testing wasn't difficult in absolute terms, since the program
> is so small, but it still took multiple tries and head-scratching, and
> the result seems unmodular compared to a Python version. Maybe there
> was something I could have done better.

Unmodular? As there is no firm specification, go for the obvious:

\ ?AT ( -- column row ) tests cursor position
: 3/5/7/n? ( n -- )
#120 1 DO
CR DUP 3 MOD 0= IF ." Fizz" ENDIF
DUP 5 MOD 0= IF ." Buzz" ENDIF
DUP 7 MOD 0= IF ." Zoom" ENDIF
?AT DROP 0= IF . ELSE DROP ENDIF
LOOP ;

-marcel

Paul Rubin

unread,
Oct 7, 2012, 2:43:00 PM10/7/12
to
m...@iae.nl (Marcel Hendrix) writes:
> Unmodular? As there is no firm specification, go for the obvious:
> \ ?AT ( -- column row ) tests cursor position

I don't think relying on the cursor position is especially modular.
Also, it extends reasonably well to n=11:

DUP 11 MOD 0= IF ." Blarg" ENDIF

but fails for n=13, which is an unlucky number and should therefore
print as the empty string.

Doug Hoffman

unread,
Oct 7, 2012, 2:45:56 PM10/7/12
to
Don't think there's anything wrong with your solution.

Here's another one I threw together just now:

: divisible-by? ( n1 n2 -- f f ) \ is n1 divisible by n2?
mod 0= dup ;

: fizz ( n -- f ) \ true if divisible
3 divisible-by? if ." Fizz" then ;

: buzz ( n -- f ) \ true if divisible
5 divisible-by? if ." Buzz" then ;

: zoom ( n -- f ) \ true if divisible
7 divisible-by? if ." Zoom" then ;

: run ( n -- )
1 do
cr i fizz
i buzz or
i zoom or
0= if i . then
loop ;

0 [if]
Comments:
- perhaps more meaningful names
- no need for return stack for keeping i
- a bit less stack thrashing, just 1 dup
- fewer items being tossed around on the stack
- fizz/buzz/zoom could be factored further, not sure it's worth it

No doubt someone else has a more clever solution.
[then]

-Doug

Marcel Hendrix

unread,
Oct 7, 2012, 3:09:54 PM10/7/12
to
Paul Rubin <no.e...@nospam.invalid> writes Re: Fizz Buzz Zoom

> m...@iae.nl (Marcel Hendrix) writes:
>> Unmodular? As there is no firm specification, go for the obvious:
>> \ ?AT ( -- column row ) tests cursor position

> I don't think relying on the cursor position is especially modular.

You mean portable?

> Also, it extends reasonably well to n=11:

> DUP 11 MOD 0= IF ." Blarg" ENDIF

> but fails for n=13, which is an unlucky number and should therefore
> print as the empty string.

I don't know anything about 13. Your program is my specification.

But I should have tested better. Here it is again with Blargh, 11 and 13.

: 3/5/7/11/n? ( -- )
#120 1 DO
CR i 3 MOD 0= IF ." Fizz" ENDIF
i 5 MOD 0= IF ." Buzz" ENDIF
i 7 MOD 0= IF ." Zoom" ENDIF
i #11 MOD 0= IF ." Blargh" ENDIF
?AT DROP 0=
I #13 <> AND IF i . ENDIF
LOOP ;

-marcel

Paul Rubin

unread,
Oct 7, 2012, 3:18:57 PM10/7/12
to
m...@iae.nl (Marcel Hendrix) writes:
>> I don't think relying on the cursor position is especially modular.
> You mean portable?

I mean separating compuational from i/o concerns, that sort of thing.

> I don't know anything about 13. Your program is my specification.

The idea was that readers would be familiar with the original FizzBuzz
problem, but maybe you weren't. My bad.

> ?AT DROP 0=
> I #13 <> AND IF i . ENDIF

This extra test seems rather ad-hoc.

Paul Rubin

unread,
Oct 7, 2012, 3:28:21 PM10/7/12
to
Doug Hoffman <glid...@gmail.com> writes:
> - perhaps more meaningful names
> - no need for return stack for keeping i
> - a bit less stack thrashing, just 1 dup
> - fewer items being tossed around on the stack
> - fizz/buzz/zoom could be factored further, not sure it's worth it

The fizz/buzz/zoom design seems intended to make the the whole
multi-level program to have the right stuff on the stack all the time,
which is in the Forth spirit, but the lack of factoring doesn't seem
ideal. More factoring would seem to require passing a lot of args to
the bottom level, like my example did.

My version originally used gforth locals instead of the return stack
stuff, but that seemed like overkill.

Doug Hoffman

unread,
Oct 7, 2012, 9:06:27 PM10/7/12
to
On 10/7/12 3:28 PM, Paul Rubin wrote:
> Doug Hoffman <glid...@gmail.com> writes:
>> - perhaps more meaningful names
>> - no need for return stack for keeping i
>> - a bit less stack thrashing, just 1 dup
>> - fewer items being tossed around on the stack
>> - fizz/buzz/zoom could be factored further, not sure it's worth it
>
> The fizz/buzz/zoom design seems intended to make the the whole
> multi-level program to have the right stuff on the stack all the time,
> which is in the Forth spirit, but the lack of factoring doesn't seem
> ideal. More factoring would seem to require passing a lot of args to
> the bottom level, like my example did.

I was thinking of something like this:

: divisible-by? ( n1 n2 -- f )
mod 0= ;

: speak? { n1 n2 str -- f }
n1 n2 divisible-by? dup if str count type then ;

: fizz ( n1 -- f )
3 c" Fizz" speak? ;

: buzz ( n1 -- f )
5 c" Buzz" speak? ;

: zoom ( n1 -- f )
7 c" Zoom" speak? ;

: run ( n -- )
1 do
cr i fizz
i buzz or
i zoom or
0= if i . then
loop ;

I'm not sure the extra factoring was worthwhile, but there it is.

> My version originally used gforth locals instead of the return stack
> stuff, but that seemed like overkill.

I will use locals with no apologies when they suit my purpose, as you
can see above.

-Doug

Paul Rubin

unread,
Oct 8, 2012, 12:32:47 AM10/8/12
to
Doug Hoffman <glid...@gmail.com> writes:
> I'm not sure the extra factoring was worthwhile, but there it is.

I see, you got rid of one of the bottom level stack items by using
counted strings instead of addr/u, and got rid of another one by
repeating the "or" after each top level divisor check. I guess both are
reasonable.

> I will use locals with no apologies when they suit my purpose, as you
> can see above.

Using them does make stuff easier. I wonder whether typical embedded
Forths support them.

oua...@gmail.com

unread,
Oct 8, 2012, 4:15:46 AM10/8/12
to
On Sunday, October 7, 2012 10:18:58 PM UTC+3, Paul Rubin wrote:
> m...@iae.nl (Marcel Hendrix) writes:
>
> >> I don't think relying on the cursor position is especially modular.
>
> > You mean portable?
>
>
>
> I mean separating compuational from i/o concerns, that sort of thing.

Hi Paul!
A-solution tests + :)

variable offs
: PAD+ ( ca u -- )
PAD offs @ tuck + swap 80 swap -
rot umin dup offs +! move ;

: i/o PAD offs @ type space ;

: main ( n -- )
1 DO
0 dup offs !
I 3 mod 0= IF s" fizz" PAD+ -1 OR THEN
I 5 mod 0= IF s" buzz" PAD+ -1 OR THEN
I 7 mod 0= IF s" zoom" PAD+ -1 OR THEN
IF I 0 <# #S #> PAD+ i/o THEN
LOOP ;

\ Tests

s" alfa" PAD+
s" beta" PAD+
PAD offs @ type
cr i/o cr
cr 120 main cr
cr .s cr bye

Have a nice day,
humptydumpty

Doug Hoffman

unread,
Oct 8, 2012, 6:06:31 AM10/8/12
to
Not hard to get rid of locals in this case (often not so easy). Locals
are in ANS, btw:

: speak? ( n1 n2 str -- f )
>r divisible-by? dup if r@ count type then r> drop ;

or maybe just cram the whole thing together, perhaps the obvious thing
to do for such a simple routine. Doing so for more complex code is
usually a mistake, IMO. A personal style thing perhaps:

: run'
120 1 do
cr i 3 mod 0= dup if ." Fizz" then
i 5 mod 0= dup if ." Buzz" then or
i 7 mod 0= dup if ." Zoom" then or
0= if i . then
loop ;

-Doug

Mark Wills

unread,
Oct 8, 2012, 8:24:04 AM10/8/12
to
>
> : run'
>    120 1 do
>       cr i 3 mod 0= dup if ." Fizz" then
>          i 5 mod 0= dup if ." Buzz" then or
>          i 7 mod 0= dup if ." Zoom" then or
>          0= if i . then
>       loop ;
>
> -Doug

At last! Somebody posted what I was thinking. I was looking at the
everyone's code thinking "Hmmm... I'm clearly not getting this"

Anton Ertl

unread,
Oct 8, 2012, 10:02:29 AM10/8/12
to
Paul Rubin <no.e...@nospam.invalid> writes:
>This works in gforth. I'm not sure if it's valid ANS Forth, since it
>requires multiple s" ..." string literals to be alive at the same time.

You only use the compilation semantics of S", and the strings from
that live forever, so no need to worry about that.

>Coding and testing wasn't difficult in absolute terms, since the program
>is so small, but it still took multiple tries and head-scratching, and
>the result seems unmodular compared to a Python version. Maybe there
>was something I could have done better.

What is the specification? What is the Python version?

- anton
--
M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard: http://www.forth200x.org/forth200x.html
EuroForth 2012: http://www.euroforth.org/ef12/

Paul Rubin

unread,
Oct 8, 2012, 11:38:44 AM10/8/12
to
an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
> What is the specification? What is the Python version?

It's a version of the famous FizzBuzz interview question:

http://www.codinghorror.com/blog/2007/02/why-cant-programmers-program.html

The original question only used "Fizz" and "Buzz" and the most common
solution involves dealing with the case n%15==0 separately from the
n%3==0 and n%5==0 cases, which I thought was ugly. There is not a
canonical Python version. There's a discussion here of a bunch of
Haskell and Python solutions that I mostly didn't like, because of too
much abstraction (the Haskell monoid versions), or not enough (the
Python version everyone liked handles the empty string incorrectly):

http://www.reddit.com/r/haskell/comments/10zlyy/fizzbuzz_revisited_using_monoids/

FizzBuzz also shows up in the coding game Code Hero:

http://primerlabs.com/fizzbosses

My preferred version separates the code from the data ("figure out the
data structure, and the code takes care of itself"). First write down
the data:

spec = [(3, "Fizz"), (5, "Buzz"), (7, "Zoom")]

The following code then flows pretty easily (be aware that ''.join(...)
is a Python idiom for concatenating a list of strings, and that a
boolean test on a list returns true iff the list is non-empty):

def fb(n):
ns = [s for (d,s) in spec if n%d==0]
return ''.join(ns) if ns else str(n)

for i in xrange(1,121): print fb(i)

This handles arbitrary numbers of divisors and does the right thing if
one or more of the strings is empty. In Haskell I'd probably code it
the right way, or if I did it with monoids I'd use mconcat instead of
that thing with monad comprehensions (don't worry if that makes no
sense).

According to the Wikipedia article about FizzBuzz which I just read last
night, it was originally a drinking game rather than a programming
challenge, and the syllable for divisor 7 is traditionally "Woof" rather
than "Zoom". One learns all kinds of things on the internets.

Paul Rubin

unread,
Oct 8, 2012, 11:43:54 AM10/8/12
to
Paul Rubin <no.e...@nospam.invalid> writes:
> In Haskell I'd probably code it the right way,

I meant to say "the same way". Freudian slip, perhaps.

A. K.

unread,
Oct 8, 2012, 12:03:55 PM10/8/12
to
Thanks, nice anecdote. :-)

Although I'd say that most of our candidates could solve the problem.
But perhaps just because we focus on university graduates who still have
some fresh math in their heads.

Anton Ertl

unread,
Oct 8, 2012, 12:08:35 PM10/8/12
to
Paul Rubin <no.e...@nospam.invalid> writes:
>an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
>> What is the specification? What is the Python version?
>
>It's a version of the famous FizzBuzz interview question:
>
> http://www.codinghorror.com/blog/2007/02/why-cant-programmers-program.html
>
>The original question only used "Fizz" and "Buzz" and the most common
>solution involves dealing with the case n%15==0 separately from the
>n%3==0 and n%5==0 cases, which I thought was ugly.

Ok, that specification is:

|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".

Given the separate case in the spec for Fizz Buzz, having a separate
case in the program looks pretty natural to me.

Anyway, what is the specification of Fizz Buzz Zoom?

Paul Rubin

unread,
Oct 8, 2012, 12:23:59 PM10/8/12
to
an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
> Given the separate case in the spec for Fizz Buzz, having a separate
> case in the program looks pretty natural to me.

Since you've already done both tests (mod 3 and mod 5), doing them again
has a DRY problem.

> |multiples of five print "Buzz". For numbers which are multiples of
> |both three and five print "FizzBuzz". ...
> Anyway, what is the specification of Fizz Buzz Zoom?

Same thing, except additionally, if n is divisible by 7, print "Zoom".
So for example, for n=21 you'd print FizzZoom, for n=35 you'd print
BuzzZoom, and for n=105 you'd print FizzBuzzZoom.

It could be treated as a two-part question:
1) solve FizzBuzz
2) extend FizzBuzz solution to handle Zoom.

Paul Rubin

unread,
Oct 8, 2012, 12:37:38 PM10/8/12
to
oua...@gmail.com writes:
> IF I 0 <# #S #> PAD+ i/o THEN

That should say

IF i/o ELSE I . THEN

other than that, the code does about what is asked, though it seems like
more coding effort than some of the other solutions.

Gerry Jackson

unread,
Oct 8, 2012, 12:50:47 PM10/8/12
to

Paul Rubin

unread,
Oct 8, 2012, 1:15:25 PM10/8/12
to
Gerry Jackson <ge...@jackson9000.fsnet.co.uk> writes:
> https://groups.google.com/forum/?hl=en&fromgroups=#!topic/comp.lang.forth/tLZLCc02gmk/overview

ZOMG. I should have figured there would be something like that. I
liked the Colorforth entry ;-).

Anton Ertl

unread,
Oct 8, 2012, 1:11:46 PM10/8/12
to
Paul Rubin <no.e...@nospam.invalid> writes:
>an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
>> Given the separate case in the spec for Fizz Buzz, having a separate
>> case in the program looks pretty natural to me.
>
>Since you've already done both tests (mod 3 and mod 5), doing them again
>has a DRY problem.

What's a "DRY problem"?

>> |multiples of five print "Buzz". For numbers which are multiples of
>> |both three and five print "FizzBuzz". ...
>> Anyway, what is the specification of Fizz Buzz Zoom?
>
>Same thing, except additionally, if n is divisible by 7, print "Zoom".
>So for example, for n=21 you'd print FizzZoom, for n=35 you'd print
>BuzzZoom, and for n=105 you'd print FizzBuzzZoom.

Now maybe someone should write a blog posting about writing
specifications. Let me have a go at that. What you mean is probably:

Write a program that prints the numbers from 1 to 121. 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". For numbers which are multiples
of seven, print "Zoom". For numbers which are multiples of three and
seven, print "FizzZoom". For numbers which are multiples of five and
seven, print "BuzzZoom". For numbers which are multiples of three,
five and seven, print "FizzBuzzZoom".

Why did I write it that way? Because it's closest to the FizzBuzz
spec and also the way you wrote the incomplete spec for FizzBuzzZoom.

That spec naturally leads to a solution you consider "ugly". Now
maybe someone can write a less ugly specification, and maybe that
naturally leads to a less ugly program.

Bernd Paysan

unread,
Oct 8, 2012, 4:12:48 PM10/8/12
to
Anton Ertl wrote:
> That spec naturally leads to a solution you consider "ugly". Now
> maybe someone can write a less ugly specification, and maybe that
> naturally leads to a less ugly program.

The only ugly think is that you somehow have to track your decision to
prevent printing the number. A slight variation of Doug Hoffman's code
doesn't look that ugly.

: mod? ( flag i n -- flag' flagp )
mod 0= tuck or swap ;

: fizzbuzzzoom ( n -- ) 1 ?DO false
i 3 mod? IF ." Fizz" THEN
i 5 mod? IF ." Buzz" THEN
i 7 mod? IF ." Zoom" THEN
0= IF i 0 .r THEN space \ or cr, if you prefer
LOOP ;

If you think this is not enough DSL, try this:

: mod? ( flag i n -- flag' flagp )
mod 0= tuck or swap ;

: ?" ( flag n "string"<"> -- flag' )
]] i swap mod? IF ." THEN [[ ; immediate

: fizzbuzzzoom ( n -- ) 1 ?DO false
3 ?" Fizz" 5 ?" Buzz" 7 ?" Zoom"
0= IF i 0 .r THEN space \ or cr, if you prefer
LOOP ;

--
Bernd Paysan
"If you want it done right, you have to do it yourself"
http://bernd-paysan.de/

Paul Rubin

unread,
Oct 8, 2012, 4:16:41 PM10/8/12
to
an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
> What's a "DRY problem"?

DRY = Don't Repeat Yourself. Basically, "this should be factored some
more".

> Write a program that prints the numbers from 1 to 121. 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". For numbers which are multiples
> of seven, print "Zoom". For numbers which are multiples of three and
> seven, print "FizzZoom". For numbers which are multiples of five and
> seven, print "BuzzZoom". For numbers which are multiples of three,
> five and seven, print "FizzBuzzZoom".

This seems like a messy specification to me, and also an incorrect or
ambiguous one, since (e.g.) for n=15 it allows printing "Fizz Buzz
FizzBuzz". Its size also grows exponentially in the number of divisors.
Here's another attempt:

Write a program that prints the numbers from 1 to 120 (inclusive) in
FBZ (Fizz-Buzz-Zoom) notation, one per line. For a natural number n
that is coprime to 3, 5, and 7, the FBZ notation for n is the same as
the decimal representation of n. For other natural numbers n, the
FBZ notation for n is the concatenation of the F, B, and Z notations
for n. The F notation for natural n is the string "Fizz" if n is a
multiple of 3, otherwise it is the empty string. The B notation for
natural n is the string "Buzz" if n is a multiple of 5, otherwise it
is the empty string. The Z notation for natural n is the string
"Zoom" if n is a multiple of 7, otherwise it is the empty string.

That is also a messy spec, but most ways I see to clean it up involve
introducing more machinery that in other ways makes it worse. I do
think the monoid implementation

fbz n = fromMaybe (show n) (mconcat [f 3 "Fizz", f 5 "Buzz", f 7 "Zoom"])
where f d s = if n`mod`d==0 then Just s else Nothing
main = map (putStrLn . fbz) [1..120]

is the most conceptually explicit reflection of how I see the problem,
even though in actual code I like the list-based version better due to
it having fewer implementation artifacts (library dependencies etc.)

Anyway, as someone in the earlier clf thread mentioned, it's an
interview question rather than a "program from spec" problem. In that
situation, some ambiguity can be a good thing since it lets you see the
candidate's approach to resolving it.

There is a formal specification language called Z-notation (nothing to
do with the "Z notation" above) that some Ada projects use. Now I'm
sort of wondering what a Z-notation spec for this problem would look
like.

> That spec naturally leads to a solution you consider "ugly". Now
> maybe someone can write a less ugly specification, and maybe that
> naturally leads to a less ugly program.

Even with a formal spec, it's still appropriate to identify the patterns
inherent in the spec and reflect them in the code.

Hannu Vuolasaho

unread,
Oct 8, 2012, 5:06:34 PM10/8/12
to
On 2012-10-08, Paul Rubin <no.e...@nospam.invalid> wrote:
>
> Anyway, as someone in the earlier clf thread mentioned, it's an
> interview question rather than a "program from spec" problem. In that
> situation, some ambiguity can be a good thing since it lets you see the
> candidate's approach to resolving it.

Indeed. This is interview test. There are many ways to answer it. It
gives idea what kind of code applicant writes and it reveals how
applicant handdles problems.

I was given this kind of question after a short interview. The task was
given on paper "Solve this problem." and there was the place to paper
code it. I had said that I knew some forth so I solved it quite fast
with forth. And I failed.

My program would work. I had even a stack comment for those three words.
What I missed was documenting what the code did, who wrote it and so on.

So far all solutions suffer the same problem. Very good programmers
here don't bother to document.

--
Hannu Vuolasaho

Elizabeth D. Rather

unread,
Oct 8, 2012, 5:57:29 PM10/8/12
to
People who "don't bother to document" are *not* "very good programmers"
in my book.

That said, expectations when you're writing code in a job interview
situation are significantly different than when tossing off a little
code to illustrate a point in a newsgroup discussion. In an interview,
it pays to bust a gut to provide clear documentation, especially when
you're using a language the interviewer isn't familiar with.

All things considered, in this thread I like Doug Hoffman's code best:

: run'
120 1 do
cr i 3 mod 0= dup if ." Fizz" then
i 5 mod 0= dup if ." Buzz" then or
i 7 mod 0= dup if ." Zoom" then or
0= if i . then
loop ;

(although I really think he should have included an empty stack
comment). All the detailed factoring in other approaches is
inappropriate for a problem this simple. In a job interview, a brief
discussion of the approach here would be important.

Cheers,
Elizabeth

--
==================================================
Elizabeth D. Rather (US & Canada) 800-55-FORTH
FORTH Inc. +1 310.999.6784
5959 West Century Blvd. Suite 700
Los Angeles, CA 90045
http://www.forth.com

"Forth-based products and Services for real-time
applications since 1973."
==================================================

oua...@gmail.com

unread,
Oct 9, 2012, 4:16:36 AM10/9/12
to
Ok! I misunderstand requirements.
But a minimal change should be:
0= IF I 0 <# #S #> THEN i/o
as you requested to have a separation between `i/o' code and computational code.

Mark Wills

unread,
Oct 9, 2012, 4:26:21 AM10/9/12
to
> Los Angeles, CA 90045http://www.forth.com
>
> "Forth-based products and Services for real-time
> applications since 1973."
> ==================================================- Hide quoted text -
>
> - Show quoted text -

I agree. And I think that, having submitted the above example in
Forth, if you were criticised in an interview for not commenting the
code, my counter would be that it is self describing to anyone with a
passing familiarity with the language to render comments superfluous.

George Hubert

unread,
Oct 9, 2012, 11:38:07 AM10/9/12
to
On Tuesday, October 9, 2012 9:26:21 AM UTC+1, M.R.W Wills wrote:
> On Oct 8, 10:57 pm, "Elizabeth D. Rather" <erat...@forth.com> wrote: > On 10/8/12 11:06 AM, Hannu Vuolasaho wrote: > > > > > > > On 2012-10-08, Paul Rubin <no.em...@nospam.invalid> wrote: > > >> Anyway, as someone in the earlier clf thread mentioned, it's an > >> interview question rather than a "program from spec" problem.  In that > >> situation, some ambiguity can be a good thing since it lets you see the > >> candidate's approach to resolving it. > > > Indeed. This is interview test. There are many ways to answer it. It > > gives idea what kind of code applicant writes and it reveals how > > applicant handdles problems. > > > I was given this kind of question after a short interview. The task was > > given on paper "Solve this problem." and there was the place to paper > > code it. I had said that I knew some forth so I solved it quite fast > > with forth. And I failed. > > > My program would work. I had even a stack comment for those three words. > > What I missed was documenting what the code did, who wrote it and so on. > > > So far all solutions suffer the same problem. Very good programmers > > here don't bother to document. > > People who "don't bother to document" are *not* "very good programmers" > in my book. > > That said, expectations when you're writing code in a job interview > situation are significantly different than when tossing off a little > code to illustrate a point in a newsgroup discussion. In an interview, > it pays to bust a gut to provide clear documentation, especially when > you're using a language the interviewer isn't familiar with. > > All things considered, in this thread I like Doug Hoffman's code best: > > : run' >    120 1 do >       cr i 3 mod 0= dup if ." Fizz" then >          i 5 mod 0= dup if ." Buzz" then or >          i 7 mod 0= dup if ." Zoom" then or >          0= if i . then >       loop ; > > (although I really think he should have included an empty stack > comment). All the detailed factoring in other approaches is > inappropriate for a problem this simple. In a job interview, a brief > discussion of the approach here would be important. > > Cheers, > Elizabeth > > -- > ================================================== > Elizabeth D. Rather   (US & Canada)   800-55-FORTH > FORTH Inc.                         +1 310.999.6784 > 5959 West Century Blvd. Suite 700 > Los Angeles, CA 90045http://www.forth.com > > "Forth-based products and Services for real-time > applications since 1973." > ==================================================- Hide quoted text - > > - Show quoted text - I agree. And I think that, having submitted the above example in Forth, if you were criticised in an interview for not commenting the code, my counter would be that it is self describing to anyone with a passing familiarity with the language to render comments superfluous.

Curious. I find it doesn't match the spec. One of the numbers 1 to 120
inclusive has been left out (it's left as an exercise for the reader to
work out which one). Most other solutions have similar problems.

George Hubert

Paul Rubin

unread,
Oct 9, 2012, 12:03:01 PM10/9/12
to
George Hubert <george...@yahoo.co.uk> writes:
> Curious. I find it doesn't match the spec. One of the numbers 1 to 120
> inclusive has been left out (it's left as an exercise for the reader to
> work out which one). Most other solutions have similar problems.

Well, that spec was written after the code examples were posted as
something of a separate exercise, so the mismatches are not the fault of
the programs.

Elizabeth D. Rather

unread,
Oct 9, 2012, 2:44:51 PM10/9/12
to
On 10/8/12 10:26 PM, Mark Wills wrote:
> On Oct 8, 10:57 pm, "Elizabeth D. Rather" <erat...@forth.com> wrote:
...
>> That said, expectations when you're writing code in a job interview
>> situation are significantly different than when tossing off a little
>> code to illustrate a point in a newsgroup discussion. In an interview,
>> it pays to bust a gut to provide clear documentation, especially when
>> you're using a language the interviewer isn't familiar with.
>>
>> All things considered, in this thread I like Doug Hoffman's code best:
>>
>> : run'
>> 120 1 do
>> cr i 3 mod 0= dup if ." Fizz" then
>> i 5 mod 0= dup if ." Buzz" then or
>> i 7 mod 0= dup if ." Zoom" then or
>> 0= if i . then
>> loop ;
>>
>> (although I really think he should have included an empty stack
>> comment). All the detailed factoring in other approaches is
>> inappropriate for a problem this simple. In a job interview, a brief
>> discussion of the approach here would be important.
...
> I agree. And I think that, having submitted the above example in
> Forth, if you were criticised in an interview for not commenting the
> code, my counter would be that it is self describing to anyone with a
> passing familiarity with the language to render comments superfluous.
>

While that defense is technically true, it can come off sounding like
you're blaming the interviewer for not understanding the code. In an
interview situation, I'd recommend not necessarily trying to describe
what each word does, but the thought process behind your design of the
solution, along the lines of:

"Set up a loop in which, for each iteration, the current index is tested
to see if it's evenly divisible by 3, 5, or 7. If it is, print the
appropriate string. Otherwise, print the value."

The interviewer wants to know how you think, as well as how you write
code. So, here's a clear explanation and some code which, given the
explanation, looks pretty clear even to someone who's never seen Forth.

Doug Hoffman

unread,
Oct 10, 2012, 9:02:01 AM10/10/12
to
On 10/8/12 11:38 AM, Paul Rubin wrote:

> My preferred version separates the code from the data ("figure out the
> data structure, and the code takes care of itself"). First write down
> the data:
>
> spec = [(3, "Fizz"), (5, "Buzz"), (7, "Zoom")]
>
> The following code then flows pretty easily (be aware that ''.join(...)
> is a Python idiom for concatenating a list of strings, and that a
> boolean test on a list returns true iff the list is non-empty):
>
> def fb(n):
> ns = [s for (d,s) in spec if n%d==0]
> return ''.join(ns) if ns else str(n)
>
> for i in xrange(1,121): print fb(i)
>
> This handles arbitrary numbers of divisors and does the right thing if
> one or more of the strings is empty.

There is a non-trivial degree of support required to use this kind of
approach in Forth. It could be approximated with some scaffolding.
Using some (of my) library code, microFMS which is about 30-40 lines of
code, with 3 existing library classes(var, string, and object-list):

\ record-class objects are specific to this problem
create record <super object
ivar d \ will contain a var obj
ivar s \ will contain a string obj
:m init: var >dict d ! stringc >dict s ! ;m
:m !: ( d stra len -- )
s @ !: d @ !: ;m
:m d: ( -- d ) d @ @: ;m
:m s: ( -- stra len ) s @ @: ;m
:m p: [char] ( emit d @ p: [char] , emit s @ p: [char] ) emit ;m
set-order

\ an object-list is a generic expandable object container
object-list >dict value spec

3 s" Fizz" record spec add!:
5 s" Buzz" record spec add!:
7 s" Zoom" record spec add!:

spec p: \ print our list
(3 ,Fizz)(5 ,Buzz)(7 ,Zoom)

\ Finally, the main program.
\ We use the each: construct to iterate
\ over the spec list.
: fb { n | rec ns -- }
stringc >heap to ns
begin
spec each:
while
to rec
n rec d: mod 0= if rec s: ns add: then
repeat
ns size: if ns p: else n . then
ns free: ;

: run ( n -- ) 1 do cr i fb loop ;

17 run
1
2
Fizz
4
Buzz
Fizz
Zoom
8
Fizz
Buzz
11
Fizz
13
Zoom
FizzBuzz
16 ok

This also handles arbitrary numbers of divisors and also does the right
thing if one or more of the strings is empty.

-Doug

Anton Ertl

unread,
Oct 10, 2012, 8:32:49 AM10/10/12
to
Paul Rubin <no.e...@nospam.invalid> writes:
>an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
>> Write a program that prints the numbers from 1 to 121. 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". For numbers which are multiples
>> of seven, print "Zoom". For numbers which are multiples of three and
>> seven, print "FizzZoom". For numbers which are multiples of five and
>> seven, print "BuzzZoom". For numbers which are multiples of three,
>> five and seven, print "FizzBuzzZoom".
>
>This seems like a messy specification to me, and also an incorrect or
>ambiguous one, since (e.g.) for n=15 it allows printing "Fizz Buzz
>FizzBuzz".

That supposed ambiguity is already there in the original
specification. It still seems that everybody interpreted it in the
same way, following the following rule: Apply the most specific rule
of those given. And actually this specific rule is necessary, because
otherwise there would be an ambiguity about what happens for numbers
divisible by three and by five; should we apply the three-rule, the
five rule, first three, then five or first five, then three. And I
guess that's why you spelled out every case of this kind explicitly in
your incomplete extension of the specification, too.

>Here's another attempt:
>
> Write a program that prints the numbers from 1 to 120 (inclusive) in
> FBZ (Fizz-Buzz-Zoom) notation, one per line. For a natural number n
> that is coprime to 3, 5, and 7, the FBZ notation for n is the same as
> the decimal representation of n. For other natural numbers n, the
> FBZ notation for n is the concatenation of the F, B, and Z notations
> for n. The F notation for natural n is the string "Fizz" if n is a
> multiple of 3, otherwise it is the empty string. The B notation for
> natural n is the string "Buzz" if n is a multiple of 5, otherwise it
> is the empty string. The Z notation for natural n is the string
> "Zoom" if n is a multiple of 7, otherwise it is the empty string.
>
>That is also a messy spec, but most ways I see to clean it up involve
>introducing more machinery that in other ways makes it worse.

It's also longer (10 lines instead of 8), more complicated and harder
to understand. Let's see what program follows naturally from it:

: fbz ( -- )
121 1 ?do
cr i 3 mod 0<> i 5 mod 0<> i 7 mod 0<> and and if
i .
else
i 3 mod 0= if ." fizz" then
i 5 mod 0= if ." buzz" then
i 7 mod 0= if ." zoom" then
then
loop ;

>Anyway, as someone in the earlier clf thread mentioned, it's an
>interview question rather than a "program from spec" problem. In that
>situation, some ambiguity can be a good thing since it lets you see the
>candidate's approach to resolving it.

Not in this case, because the interviewer was interested in
correctness. I also don't think that ambiguity lets you see a
candidates approach any better than non-ambiguity.

>Even with a formal spec, it's still appropriate to identify the patterns
>inherent in the spec and reflect them in the code.

If the point of the example is correctness (as in the original
example), staying close to the spec is a good idea, as it reduces the
opportunities for mistakes. If the goal is to have the solution fast,
staying close to the spec is also a good idea. If the point of the
example is to produce small or efficient code, then one should also
consider other approaches.

Concerning the goal of future-proofing the program, I find that hard
for a contrived problem; you contrive one extension of the problem,
but someone else might contrive a completely different one, and there
is no way to predict which contrived extension is more probably.
Besides, future-proofing is not the Forth way:-).

Bernd Paysan

unread,
Oct 10, 2012, 10:59:45 AM10/10/12
to
Anton Ertl wrote:
> Concerning the goal of future-proofing the program, I find that hard
> for a contrived problem; you contrive one extension of the problem,
> but someone else might contrive a completely different one, and there
> is no way to predict which contrived extension is more probably.
> Besides, future-proofing is not the Forth way:-).

But extensibility is. Let's say the general problem is "print some word
if there is a rule to match, print a number if there isn't". Typical
application would be a calendar, where you have a date on normal days,
and a string if there is an entry (multiple strings for multiple
entries).

So you write

Defer check ( flag n -- flag' ) ' drop is check

: list-items ( n1 n2 -- )
?DO false i check 0= IF i 0 .r THEN cr LOOP ;

\ check DSL syntactic sugar

: check[ :noname ]] dup >r [[ ['] check defer@ compile, postpone r> ;
: ]check" ]] dup IF ." THEN or ; [[ is check ; immediate

\ silly checks to test the program

check[ 3 mod 0= ]check" Fizz"
check[ 5 mod 0= ]check" Buzz"
check[ 7 mod 0= ]check" Zoom"

121 1 list-items

Being able to write a small program as test can be quite useful in a
calendar - I could add a check for fourth Thursday in the month, but
earlier than 24th of December with the string "Munich Forth meeting" or
8th month, 25th day in the Chinese lunar calender (today) as "Peng
XiuQing's birthday". The further is almost possible in most calendars,
with the exeption of 24th of December, the latter usually isn't, because
the calendar makers think there is only one calendar system...

You probably want to write "Sunday" instead of "Zoom". And to make this
a calendar application, you will implement a few words which convert the
index (e.g. 0=first of March of the year 1 BC, Julian calendar) to year-
month-day in various calendars. And maybe some strings want some
variables in it, the birthday reminder could contain the age.

A lot of flexibility comes from having source code available, so
extensions or modifications are easy to do. E.g. if I dedice that it is
better to append the string to a string variable instead of printing it,
I only have to change ]CHECK" and LIST-ITEMS.

Anton Ertl

unread,
Oct 10, 2012, 12:42:25 PM10/10/12
to
Bernd Paysan <bernd....@gmx.de> writes:
>Anton Ertl wrote:
>> Concerning the goal of future-proofing the program, I find that hard
>> for a contrived problem; you contrive one extension of the problem,
>> but someone else might contrive a completely different one, and there
>> is no way to predict which contrived extension is more probably.
>> Besides, future-proofing is not the Forth way:-).
>
>But extensibility is. Let's say the general problem is

I actually meant Chuck Moore's way. And I think he would not
generalize the problem and design extensibility for that on first
encounter. Instead, he would see a more general problem and design a
more general program for it only if he encounters several similar
problems.

While I usually lean more in the direction of generalization than most
Forthers, in this case I would just follow Chuck Moore's way, because
it is a contrived problem, and any generalization would be
even more guesswork than usual.

>"print some word
>if there is a rule to match, print a number if there isn't".

Which does not specify what happens if several rules match. Paul
Rubin generalized the FizzBuzz problem in a way that answers that
question in a specific way, but what if the interviewer had next
asked: "Now change the program such that it prints 'boing' if the
number is divisible by three and by five". The "ugly" program would
be easy to fix, while the generalizations along the lines that Paul
Rubin and you have in mind would require more effort.

>Defer check ( flag n -- flag' ) ' drop is check
>
>: list-items ( n1 n2 -- )
> ?DO false i check 0= IF i 0 .r THEN cr LOOP ;
>
>\ check DSL syntactic sugar
>
>: check[ :noname ]] dup >r [[ ['] check defer@ compile, postpone r> ;
>: ]check" ]] dup IF ." THEN or ; [[ is check ; immediate
>
>\ silly checks to test the program
>
>check[ 3 mod 0= ]check" Fizz"
>check[ 5 mod 0= ]check" Buzz"
>check[ 7 mod 0= ]check" Zoom"
>
>121 1 list-items

Cute, because it allows writing checks in different files, or
interspersed with other colon definitions; useful for allowing
extensions by several people.

>A lot of flexibility comes from having source code available, so
>extensions or modifications are easy to do. E.g. if I dedice that it is
>better to append the string to a string variable instead of printing it,
>I only have to change ]CHECK" and LIST-ITEMS.

But your approach is particularly valuable in cases where one does not
want to change the original source code. If I can change the source
code, some of the other variants are just as easy to change and easier
to understand.

Paul Rubin

unread,
Oct 13, 2012, 12:18:44 AM10/13/12
to
an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
> That supposed ambiguity is already there in the original
> specification. It still seems that everybody interpreted it in the
> same way, following the following rule: Apply the most specific rule
> of those given.

I'd say the codinghorror post isn't an actual specification, but just an
informal description.

>>That is also a messy spec...
> It's also longer (10 lines instead of 8), more complicated and harder
> to understand.

Well, harder to understand is in the eye of the beholder. I do think
it's more precise.

> Let's see what program follows naturally from it: ...

That program looks ok; my version basically lifted the divisor checks
to do each one once instead of twice.

> Not in this case, because the interviewer was interested in
> correctness. I also don't think that ambiguity lets you see a
> candidates approach any better than non-ambiguity.

Part of interviewing is describing a general problem and seeing
what kind of questions the person asks.

> If the point of the example is correctness (as in the original
> example), staying close to the spec is a good idea, as it reduces the
> opportunities for mistakes. If the goal is to have the solution fast,
> staying close to the spec is also a good idea. If the point of the
> example is to produce small or efficient code, then one should also
> consider other approaches.

In this particular case I was taking the approach of trying to identify
a "pure" version of the problem, like that blog post I linked
("FizzBuzz, A Deep Navel to Gaze Into"), not particularly as
optimization or future-proofing. I think your extension:

> ... "Now change the program such that it prints 'boing' if the number
> is divisible by three and by five".

still fits into that monoidal picture (the tests just become a little
more complicated), but yeah, the factored Forth implementation suffers.

It's interesting, I keep seeing Chuck's stuff saying to factor
relentlessly so I tried to do that, but per yours and other posts in the
thread, it was probably overdone.

As for following specs in general, some specs (like ITU specs) for
policy reasons (I think this means "protecting incumbents") are written
to avoid giving any implementation guidance at all. So such a spec
might say how to recognize the correct Fizzbuzz output in a purely
syntactic, descriptive way, in this case perhaps something like EBNF
notation. I've haven't had to deal with this type of thing directly so
far though.

Elizabeth D. Rather

unread,
Oct 13, 2012, 3:06:56 AM10/13/12
to
On 10/12/12 6:18 PM, Paul Rubin wrote:
> an...@mips.complang.tuwien.ac.at (Anton Ertl) writes:
...
>> ... "Now change the program such that it prints 'boing' if the number
>> is divisible by three and by five".
>
> still fits into that monoidal picture (the tests just become a little
> more complicated), but yeah, the factored Forth implementation suffers.
>
> It's interesting, I keep seeing Chuck's stuff saying to factor
> relentlessly so I tried to do that, but per yours and other posts in the
> thread, it was probably overdone.
>
> As for following specs in general, some specs (like ITU specs) for
> policy reasons (I think this means "protecting incumbents") are written
> to avoid giving any implementation guidance at all. So such a spec
> might say how to recognize the correct Fizzbuzz output in a purely
> syntactic, descriptive way, in this case perhaps something like EBNF
> notation. I've haven't had to deal with this type of thing directly so
> far though.

Factoring isn't an abstract exercise. It should be thoroughly
functional, identifying factors that are either usable elsewhere in the
application or will facilitate testing in some fashion.

The excessively factored versions of FizzBuzz were abstract -- segments
of code carved out because they *could* be. The overall result was
*more* complex than it needed to be. The result of a *well-factored*
program should be simpler, not more complex.

Getting the additional spec may illuminate productive additional
factoring to avoid needless repetitive code. When an additional spec
reveals inappropriate factoring, it's time to re-think.

WJ

unread,
Feb 28, 2013, 9:37:32 PM2/28/13
to
Paul Rubin wrote:

> This works in gforth. I'm not sure if it's valid ANS Forth, since it
> requires multiple s" ..." string literals to be alive at the same time.
> Coding and testing wasn't difficult in absolute terms, since the program
> is so small, but it still took multiple tries and head-scratching, and
> the result seems unmodular compared to a Python version. Maybe there
> was something I could have done better.
>
> : fb0 ( flag sa su n d -- flag )
> mod 0= if type -1 else 2drop 0 then or ;
>
> : fb ( n -- flag ) >r
> 0 ( flag )
> s" Fizz" r@ 3 fb0
> s" Buzz" r@ 5 fb0
> s" Zoom" r> 7 fb0 ;
>
> : f ( n -- ) dup fb ( flag ) 0= if . else drop then cr ;
>
> : run ( -- ) \ run to 120 so that 3*5*7=105 is included
> 120 1 do i f loop ;

Factor:

USING: locals math.ranges ;

:: maybe ( m n str -- )
m n mod zero? [ str write "N" off ] when ;

: fizz ( -- )
1 105 1 <range>
[ dup "N" set
[ 3 "Fizz" maybe ] [ 5 "Buzz" maybe ] [ 7 "Zoom" maybe ] tri
"N" get [ . ] [ nl ] if*
] each ;

WJ

unread,
Feb 28, 2013, 11:51:19 PM2/28/13
to
WJ wrote:

> : fizz ( -- )
> 1 105 1 <range>

That could be:

105 [1,b]

Doug Hoffman

unread,
Mar 1, 2013, 5:38:20 AM3/1/13
to
: run
106 1 do

WJ

unread,
Mar 1, 2013, 11:23:52 AM3/1/13
to
A simpler way:

:: maybe ( m n str -- m )
m n divisor? [ str write "fizzed" on ] when
m ;

: fizz ( -- )
105 [1,b]
[ "fizzed" off
3 "Fizz" maybe
5 "Buzz" maybe
7 "Zoom" maybe
"fizzed" get [ drop nl ] [ . ] if
] each ;

WJ

unread,
Mar 1, 2013, 11:36:35 AM3/1/13
to
Another way.

:: maybe ( m bool n str -- m bool )
m n divisor?
[ str write m t ]
[ m bool ]
if ;

: fizz ( -- )
105 [1,b]
[ f
3 "Fizz" maybe
5 "Buzz" maybe
7 "Zoom" maybe

Coos Haak

unread,
Mar 1, 2013, 2:49:28 PM3/1/13
to
> Paul Rubin wrote:
>
>> This works in gforth. I'm not sure if it's valid ANS Forth, since it
>> requires multiple s" ..." string literals to be alive at the same time.

I can't find your original posting, but why complain over multiple s" ..."
string literals? In the following these are compiled and can't therefore
overlap.

>>
>> : fb0 ( flag sa su n d -- flag )
>> mod 0= if type -1 else 2drop 0 then or ;
>>
>> : fb ( n -- flag ) >r
>> 0 ( flag )
Here,
>> s" Fizz" r@ 3 fb0
here,
>> s" Buzz" r@ 5 fb0
and here
>> s" Zoom" r> 7 fb0 ;

There might be problems in some implementations if these strings were
interpreted. But not in a colon definition.

--
Coos

CHForth, 16 bit DOS applications
http://home.hccnet.nl/j.j.haak/forth.html
0 new messages