Saturday, August 6, 2011

FizzBuzz

The "new classic" programming test seems to be the FizzBuzz problem. I think it was first proposed in a blog post from 2007.

Now that it has garnered so much awareness (even videos on YouTube!), it's probably not a good interview question anymore. However, now that implementations have been written in many languages, it can be used to learn and compare the syntax of new languages.

Implementation

We can see how it works in Python:
>>> for i in range(1,101):
        if not i % 15:
            print "FizzBuzz"
        elif not i % 3:
            print "Fizz"
        elif not i % 5:
            print "Buzz"
        else:
            print i
A similar version in Clojure:
=>(defn multiple? [n div]
    (= 0 (mod n div)))

=>(doseq [i (range 1 101)]
     (cond (and (multiple? i 3)(multiple? i 5))
             (println "FizzBuzz")
           (multiple? i 3)
             (println "Fizz")
           (multiple? i 5)
             (println "Buzz")
       :else (println i)))
And, finally, a version in Factor:
( scratchpad ) 100 [1,b] [
                   {
                       { [ dup 15 divisor? ] [ drop "FizzBuzz" ] }
                       { [ dup 3  divisor? ] [ drop "Fizz"     ] }
                       { [ dup 5  divisor? ] [ drop "Buzz"     ] }
                       [ present ]
                   } cond print
               ] each

Improvements

Let's see if we can improve the Factor version a bit. First, we can "factor out" the FizzBuzz logic into its own function (showing how it would look if you were to code this directly into the listener):
( scratchpad ) : fizzbuzz ( n -- )
                   {
                       { [ dup 15 divisor? ] [ drop "FizzBuzz" ] }
                       { [ dup 3  divisor? ] [ drop "Fizz"     ] }
                       { [ dup 5  divisor? ] [ drop "Buzz"     ] }
                       [ present ]
                   } cond print ;

( scratchpad ) 100 [1,b] [ fizzbuzz ] each
To avoid all the dup and drop words, we could build a variation of cond that acts a bit like a case. The "cond-case" word was suggested on the Factor mailing list, and this is one variant of it:
MACRO: cond-case ( assoc -- )
    [
        dup callable? not [
            [ first [ dup ] prepose ]
            [ second [ drop ] prepose ] bi 2array
        ] when
    ] map [ cond ] curry ;
Using cond-case, we can improve the original version:
: fizzbuzz ( n -- )
    {
        { [ 15 divisor? ] [ "FizzBuzz" ] }
        { [ 3  divisor? ] [ "Fizz"     ] }
        { [ 5  divisor? ] [ "Buzz"     ] }
        [ present ]
    } cond-case print ;
If we realize that the check for divisible by 15 is the same as checking for divisible by 3 and divisible by 5, we can implement it slightly differently, without a cond or case:
: fizz ( n -- str/f )
    3 divisor? "Fizz" and ;

: buzz ( n -- str/f )
    5 divisor? "Buzz" and ;

: fizzbuzz ( n -- )
    dup [ fizz ] [ buzz ] bi "" append-as
    [ present ] [ nip ] if-empty print ;
Is it better? Can you think of any way to make it simpler? Perhaps by using or inventing some higher-level concepts like we did with cond-case?

1 comment:

  1. I think that `cond-case` should definitely be in the combinators vocabulary. Seriously, `cond` is ugly and redundant in this common case.

    ReplyDelete