あどけない話

インターネットに関する技術的な話など

Emacs Lisp で FizzBuzz

Emacs Lisp の習得を目指す誰かの参考になるかもしれないので、僕なりの解答を書いておきます。

アルゴリズムの実装

繰り返しと仕事は分離したいので、まず仕事の部分を定義します。

(defun fizzbuzz (num)
  (cond
   ((= (% num 15) 0) "FizzBuzz")
   ((= (% num  3) 0) "Fizz")
   ((= (% num  5) 0) "Buzz")
   (t num)))

(fizzbuzz 1)  ;; => 1
(fizzbuzz 3)  ;; => "Fizz"
(fizzbuzz 5)  ;; => "Buzz"
(fizzbuzz 15) ;; => "FizzBuzz"

whileループ

Emacs Lisp で一番現実的なのは、while 分による繰り返しです。

(defun fizzbuzz1 (limit)
  (let ((i 1) ret)
    (while (<= i limit)
      (setq ret (cons (funcall 'fizzbuzz i) ret))
      (setq i (1+ i)))
    (nreverse ret)))

(fizzbuzz1 15)
;; => (1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

マクロ

マクロで抽象化してみましょう。

(defmacro times-list (counter limit &rest body)
  `(let ((,counter 1) -temp-ret-)
     (while (<= ,counter ,limit)
       (setq -temp-ret- (cons ,@body -temp-ret-))
       (setq ,counter (1+ ,counter)))
     (nreverse -temp-ret-)))

(defun fizzbuzz2 (limit)
  (times-list i limit (funcall 'fizzbuzz i)))

(fizzbuzz2 15)
;; => (1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

末尾再帰

Emacs Lisp には末尾再帰の最適化がないにも関わらず、末尾再帰してみます。

(defun fizzbuzz3 (limit)
  (fizzbuzz3-recurse 1 limit))

(defun fizzbuzz3-recurse (i limit)
  (if (> i limit)
      nil
    (cons (funcall 'fizzbuzz i) (fizzbuzz3-recurse (1+ i) limit))))

(fizzbuzz3 15)
;; => (1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")

map

Emacs Lisp には遅延評価がないにも関わらず、1 から limit までの数のリストを生成して、map してみます。

(defun generate-list (limit)
  (let (ret)
    ;; dotimes は 0 から数え始める
    (dotimes (i limit (cdr (nreverse (cons limit ret))))
      (setq ret (cons i ret)))))

(defun fizzbuzz4 (limit)
  (mapcar 'fizzbuzz (generate-list limit)))

(fizzbuzz4 15)
;; => (1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz")