8
\$\begingroup\$

I had to jump on the bandwagon for this one. The task does a great job of helping you learn a language you're unfamiliar with.

So today I present you with my version of the infamous FizzBuzz game in Common Lisp. This is really the first "program" I've made in CLISP, and even though it's pretty small, I'm quite proud!!

I'd really love to know if there's an even easier way to make the same functionality. I notice a lot of repetition in terms of is-multiple, so if anyone has any ideas how I could DRY up all that, I'd appreciate it. Also, is there a formatting standard for symbol names and such in Lisp?

fizzbuzz.lisp

(defun is-multiple (i against)
  (= (mod i against) 0))
(defun num-action (i)
  (cond ((and (is-multiple i 3) (is-multiple i 5)) (print "FizzBuzz"))
    ((is-multiple i 3) (print "Fizz"))
    ((is-multiple i 5) (print "Buzz"))
    (T (print i))))
(dotimes (i 101) (num-action i))

Can it be cleaned up any further?

\$\endgroup\$
  • 1
    \$\begingroup\$ Minor note: if a cond clause has only one form, you can simply make it the test form. I.e., instead of (T (print i)), you can simply have ((print i)). \$\endgroup\$ – Joshua Taylor Aug 13 '14 at 15:31
  • 1
    \$\begingroup\$ In Common Lisp predicates are typically named with p or -p suffix, depending on whether they have a - in the name, so your predicate would be divisiblep. Common Lisp already has a zerop function, so you'd just do (zerop (mod n divisor)). \$\endgroup\$ – Joshua Taylor Aug 13 '14 at 15:33
7
\$\begingroup\$

Do not recompute is-multiple repeatedly by either binding the value:

(defun num-action (i)
  (let ((i3 (is-multiple i 3))
        (i5 (is-multiple i 5)))
    (cond ((and i3 i5) (print "FizzBuzz"))
          (i3 (print "Fizz"))
          (i5 (print "Buzz"))
          (T (print i)))))

or by using if:

(defun num-action (i)
  (if (is-multiple i 3)
      (if (is-multiple i 5)
          (print "FizzBuzz")
          (print "Fizz"))
      (if (is-multiple i 5)
          (print "Buzz")
          (print i))))

PS. please fix indentation

\$\endgroup\$
  • \$\begingroup\$ Interesting, I find your second option much more readable though! \$\endgroup\$ – Alex L Jul 13 '14 at 17:47
3
\$\begingroup\$

Not readable, but about as tight as it gets. I used SBCL to develop it, but CLisp seems to work just fine.

(defun is-multiple (i against) 
    (= (mod i against) 0))
(defun fizzbuzz (i)
  (let ((p (concatenate 'string (if (is-multiple i 3) "Fizz" "") (if (is-multiple i 5) "Buzz" ""))))
    (print (if (string= p "") i p))))

(dotimes (i 101) (fizzbuzz i))

The trick is to recognize that "FizzBuzz" is just an assembly of "Fizz" and "Buzz", and "BuzzFizz" is never a valid answer. So just build the string as needed, and if you didn't build anything then print the number.

\$\endgroup\$
3
\$\begingroup\$

In addition to the other posts, I'd suggest taking a look at the various print functions. In this case we don't want newlines immediately, but just at the end of the function, thus using TERPRI for a newline is useful, as well as PRINC to print values in possibly un-READ-able form (here unquoted strings).

Next, WHEN (and UNLESS) are awesome if you just have one case if the IF to handle.

Lastly, FizzBuzz is usually run from 1 to 100, so I've changed that as well.

(defun multiplep (i against)
  (zerop (mod i against)))

(defun num-action (i)
  (let ((i3 (multiplep i 3))
        (i5 (multiplep i 5)))
    (cond
      ((or i3 i5)
       (when i3
         (princ "Fizz"))
       (when i5
         (princ "Buzz")))
      ((princ i))))
  (terpri))

(dotimes (i 100)
  (num-action (1+ i)))
\$\endgroup\$
2
\$\begingroup\$

loop sorcery, cyclic lists and other black magic:

(defun fizz-buzz-direct ()
  (loop :with ring :=
     (loop :for i :from 1 :below 15
        :if (zerop (mod i 3))
        :collect (lambda (x)
                   (declare (ignore x)) "Buzz") :into result
        :else :if (zerop (mod i 5))
        :collect (lambda (x)
                   (declare (ignore x)) "Fizz") :into result
        :else :collect 'identity :into result
        :finally
        (return (nconc
                 (setf result
                       (cons (lambda (x) (declare (ignore x)) "FizzBuzz") result))
                 result)))
     :for i :from 1 :below 101
     :do (print (funcall (car (setf ring (cdr ring))) i))))

As per request. This code isn't really "better" (in what universe do FizzBuzz programs have the betterness metric?). But, just for the sake of argument, here's one way to think of it as being better than something else:

Assume that overall complexity of the FizzBuzz algorithm is dominated by the number of modulo function invocations (probably false, because printing will be a whole lot slower). Yet, for the sake of argument, assume that we wanted to do as few modulo calls as possible, then in the code above we only do 5 + 10 * 2 = 25 calls to the modulo function (and then we reuse the results obtained during the preparation stage). So that if we were to print 1000 FizzBuzz lines, we would still be doing only 25 calls to the modulo function.

This is while all other examples will end up with at best 100 / 15 + (100 - (100 / 15)) / 3 + (100 - (100 / 15) - (100 - (100 / 15)) / 3) / 5 = something like 50. And the number will keep growing as you type more lines of the output.

\$\endgroup\$
0
\$\begingroup\$

Using circular structures (note, I would not necessarily write this code in an interview situation...):

(defun fizzbuss (n)
  (loop for i from 1 to n
    for fmt in '#1=("~d" "~d" "fizz" "~d" "buzz"   ; 1-- 5
            "fizz" "~d" "~d" "fizz" "buzz" ; 6 -- 10
            "~d" "fizz" "~d" "~d" "fizzbuzz" . #1#) ; 11 -- 15, wrap
    do (format t fmt i) 
    do (format t "~%")))

This creates a circular list that will be used as a format string. We could, in principle, move the "~%" into each format string, but meh.

This loops from 1 to n, abusing the circular list to pick a format string ("print a number", "fizz", "buzz" or "fizzbuzz", we only need 15 entries to span the 15 possible options), then prints that using format (and for any of "fizz", "buzz" or "fizzbuzz", also a useless extra argument).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.