Home   Archive   Permalink



Rebol vs. FizzBuzz

There's a programming problem that someone came up with, and people have supposedly found it to confound a surprising percentage of interview candidates. It goes like this:
    
     "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'."
    
While it seems quite easy (and well...*is* easy), many programmers will want to express the string "Fizz" and "Buzz" just once each in the program, while doing the tests for divisibility just once each. That's not something most languages can do. If you want to see the carnival of workarounds in various languages, here's a wiki link where some people discuss the problem:
    
http://wiki.c2.com/?FizzBuzzTest
    
HOW WELL CAN REBOL2 DO?
    
This is the kind of problem that the Rebol evaluation model would seem suited at factoring the way people would want. Rebol2 seems like it might be able to do it with something like:
    
     repeat n 100 [
         print any [
             rejoin [
                if 0 = mod n 3 ["Fizz"]
                if 0 = mod n 5 ["Buzz"]
             ]
             n
         ]
     ]
    
It's a pattern that would *almost* work, foiled by REJOIN stringifying the nones instead of propagating them as a none return result:
    
     >> rejoin [none none]
     == "nonenone"
    
Let's imagine for the moment a variant of REJOIN which always produces a STRING! (like AJOIN) but that returns a NONE! if no values in the block reduce to anything besides NONE!. (Note that REJOIN has a number of problems when used for strings: https://forum.rebol.info/t/rejoin-ugliness-and-the-usefulness-of-tests/248 )
    
I'll call this new operation UNSPACED:
    
     unspaced: func [b [block!]] [
         b: reduce b
         remove-each item b [none? item]
         if not empty? b [ajoin b]
     ]
    
Changing the FizzBuzz code above to use UNSPACED instead of REJOIN, we'll get a working solution.
    
WHAT DOES REN-C BRING TO THE TABLE?
    
Ren-C has UNSPACED defined "in the box", as well as SPACED. Both of these are specializations of a generic DELIMIT operator. You could make more specializations:
    
     comma'd: specialize 'delimit [delimiter: ", "]
    
     >> comma'd ["a" if false ["b"] "c"]
     == "a, c"
    
     >> comma'd ["a" case [1 > 2 ["b"] 2 > 3 ["c"]] "d"]
     == "a, d"
    
But it also has a significantly more powerful model for infix. Not only can it drive generic THEN and ELSE operations which complete their left hand side and check for the absence of a value, but comparison operators like = can also complete their left hand side...offering more natural precedence. MOD is also infix. COUNT-UP acts like REPEAT, with a complementary COUNT-DOWN operation.
    
So it shapes up like:
    
     count-up n 100 [
         print [
             unspaced [
                 if n mod 3 = 0 ["Fizz"]
                 if n mod 5 = 0 ["Buzz"]
             ] else [n]
         ]
     ]
    
It's the same idea, *evolved*. (The details about how this is driven by a complete absence of value (NULL) opposed to a "falsey" NONE! are beyond the scope of this post, but are fairly epic.)
    
All told, it's nice to see a kind of coup-de-gras against FizzBuzz, where each word of code can be mapped back to the problem statement. This is no trick--things haven't been designed just to make FizzBuzz--the components are just elegant enough that this is one of the many problems it addresses cleanly.
    
WANT MORE EXAMPLES OF "REBOL: EVOLVED"?
    
There's plenty, but here's a nice one on how `--` and `??` are being reshaped for debug output:
    
https://forum.rebol.info/t/taking-a-thrilling-tour-through-the-dump/909
    
And hey, look...I know people are envious...but envy isn't necessary. They're good ideas--just respect them as such, and you can use them too.
    
https://gitter.im/red/red?at=5bee8650ddad8777ef711792
https://gitter.im/red/red?at=5bef04f6de42d46bba754aa5

posted by:   Fork     16-Nov-2018/18:27:18-8:00



Exciting stuff!

posted by:   Edoc     19-Nov-2018/13:13:55-8:00



Though UNSPACED stuff may look clever from a language-designer perspective, it's not a good solution for FizzBuzz as a real-world problem from an application designer's perspective.
    
Real world coding solutions have to be built not to exactly fit the initial "problem space". They need to be flexible enough to be maintained by several generations of later programmers - many of whom will be working to tight deadlines while sleep-deprived. And they are likely to be working in one of dozens of legacy languages scattered across their company's appscape. They will not be domain experts in many of those languages.
    
Simplicity in maintenance is prioritized in such cases - which represent 99% of the program's useful lifecycle.
    
A simple, obvious change request for BUZZFIZZ is "print nothing for multiples of 7".
    
A straight-forwardly coded version can handle that with ease:
    
ORIGINAL FIZZBUZZ
    repeat n 100 [
     triggered: false
     if 0 = mod n 3 [prin "Fizz" triggered: true]
     if 0 = mod n 5 [prin "Buzz" triggered: true]
     print either triggered [""][n]
     ]
    
MODIFIED FOR SILENT SEVENS
    repeat n 100 [
     triggered: false
     if 0 = mod n 7 [continue]
     if 0 = mod n 3 [prin "Fizz" triggered: true]
     if 0 = mod n 5 [prin "Buzz" triggered: true]
     print either triggered [""][n]
     ]    
    
Ditto, it is trivial handle "Print nothing for multiples of 7 unless they also trigger (only) a FIZZ response". Etc.
    
One valuable insight into programming in the large is that the real world is entirely edge cases all the way down. So any long-term usable language needs the flexibility to handle unlimited edge cases with aplomb.
    
Languages that can only tightly fit to original specifications are unlikely to do that.

posted by:   Mason     25-Nov-2018/16:46:09-8:00



> Though UNSPACED stuff may look clever from
> a language-designer perspective, it's not a
> good solution for FizzBuzz as a real-world
> problem from an application designer's
> perspective.
    
You just need to combine it with the right operation for your purpose. These future spec changes are in your imagination after all--since it's a fixed problem with no context.
    
But were we to go with what your imagination seems to want to maybe change in the future, maybe COLLECT is a better fit:
    
     count-up n 100 [
         print [
             unspaced collect [
                 if n mod 7 = 0 [continue]
                 if n mod 3 = 0 [keep "Fizz"]
                 if n mod 5 = 0 [keep "Buzz"]
             ] else [n]
         ]
     ]
    
But there's no reason to throw in that COLLECT until you need it. I feel that it's the right level of response in change to spec, and natural.
    
So I disagree that the approach only "looks clever". It most certainly *is* clever. :-P

posted by:   Fork     26-Nov-2018/8:56:03-8:00



DISCLOSURE:
    
Many moons ago, as a kiddo, I worked for a short while as a paid programmer. My formal training consists of two completed courses [ {Intro to Comp Sci, CSI 101}{Data Structures, CSI 310} ]. I was a distracted student in those days and my effort, hence my grades reflected such.
    
    
IN RED 0.6.4
    
;; init at 0 and increment at the top because until must run once
;; and tests for truth at the last value of block, i.e. , is n = 100
;; is? is a word in my Red instance
;; source, ex-spec: [either equal? a b [true] [false]]
;; integers are whole numbers
    
n: 0
until [
     n: n + 1
     case [
         all [ integer? n | 3 not integer? n | 5] [print 'fizz ]
         all [ integer? n | 5 not integer? n | 3] [print 'buzz]
         all [ integer? n | 3 integer? n | 5] [print 'fizzbuzz]
         print n
     ]
     is? n 100
]

posted by:   Stone Johnson     20-Aug-2019/21:31:36-7:00



Addendum:
    
| is a word of my Red instance, which returns a float! in Red because Red only returns the whole number part of a quotient when dividing and tosses away the fractional part.
    
    
    
USAGE:
     value1 / value2
    
DESCRIPTION:
     Returns the quotient of two values.
     / is an op! value.
    
ARGUMENTS:
     value1     [number! char! pair! tuple! vector! time!] "The dividend (numerator)."
     value2     [number! char! pair! tuple! vector! time!] "The divisor (denominator)."
    
RETURNS:
     [number! char! pair! tuple! vector! time!]
    
    
REBOL 2.7.8 does not have this problem.

posted by:   Stone Johnson     20-Aug-2019/23:46:54-7:00



Hi, I am working on a rebol(+ factor + shell) inspired language, and I cheated a little and made a specific function for fizzbuzz problem when I was at it (a year or more back):
    
    
        for range 1 100 { :n
         cases "" {
            { n .divides 3 } { "Fizz" }
            { n .divides 5 } { + "Buzz" }
            none { n }
         } |prn
        }
        // outputs: 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 ...
    
It's one of the examples on rye's website, but latest updates are on the blog https://ryelang.blogspot.com

posted by:   Janko M.     7-Dec-2021/6:30:01-8:00



I added a version in Meta here:
https://language.metaproject.frl/#examples

posted by:   Kaj     13-Dec-2021/15:38:19-8:00



Wow, that is a nice solution with Any. I haven't thought of that :).

posted by:   Janko M.     16-Dec-2021/5:13:14-8:00



Thanks! It's a bit more optimal, but a bit less straightforward, so not the first version to teach to beginners.

posted by:   Kaj     16-Dec-2021/7:03:14-8:00



Name:


Message:


Type the reverse of this captcha text: "? d n i b"



Home