プログラマの最低限の能力を測る試験として Fizz Buzz という問題が知られている。
基本的には 1 から 100 までの数値を表示するのだが、
- 数値が 3 で割りきれるときは数値のかわりに Fizz と表示する
- 数値が 5 で割りきれるときは数値のかわりに Buzz と表示する
- 数値が 3 と 5 の両方で割りきれるとき (つまり 3 と 5 の最小公倍数である 15 で割りきれるとき) は数値のかわりに FizzBuzz と表示する
という規則を実現するプログラムを書くというものだ。
しばしば技巧的な要素を盛り込んで遊ぶ題材としても用いられる。
これを Common Lisp でなるべく高速に処理しようと試みている記事を先日読んだ。
Common Lispで最速のfizzbuzzを実装した話 - gos-k’s blog
要するに、実行するよりも前に文字列を組み立てておき、実行時には構築済みの文字列を表示するだけというメカニズムである。
私は、 Scheme (R6RS) で同様のことをしようとするとどうなるか考えてみた。
(import (rnrs))
(define-syntax make-fizzbuzz-string
(lambda(ctx)
(syntax-case ctx ()
((_ n)
(integer? (syntax->datum #'n))
(call-with-string-output-port
(lambda(port)
(do ((i 1 (+ i 1)))
((< (syntax->datum #'n) i))
(display
(cond
((zero? (mod i 15)) "fizzbuzz")
((zero? (mod i 5)) "buzz")
((zero? (mod i 3)) "fizz")
(else i))
port)
(newline port))))))))
(define (fizzbuzz)
(display (make-fizzbuzz-string 100)))
(fizzbuzz)
Guile で逆アセンブルするとこうなる。 (当初は Chez Scheme を使おうとしたのだが、逆アセンブル機能が提供されていなかった。)
0 (assert-nargs-ee/locals 0) ;; 0 args, 0 locals
2 (toplevel-ref 1) ;; #<procedure display (object #:optional port)>
4 (object-ref 2) ;; "1\n2\nfizz\n4\nbuzz\nfizz\n7\n8\nfizz\nbuzz\n11\nfizz\n13\n14\nfizzbuzz\n16\n17\nfizz\n19\nbuzz\nfizz\n22\n23\nfizz\nbuzz\n26\nfizz\n28\n29\nfizzbuzz\n31\n32\nfizz\n34\nbuzz\nfizz\n37\n38\nfizz\nbuzz\n41\nfizz\n43\n44\nfizzbuzz\n46\n47\nfizz\n49\nbuzz\nfizz\n52\n53\nfizz\nbuzz\n56\nfizz\n58\n59\nfizzbuzz\n61\n62\nfizz\n64\nbuzz\nfizz\n67\n68\nfizz\nbuzz\n71\nfizz\n73\n74\nfizzbuzz\n76\n77\nfizz\n79\nbuzz\nfizz\n82\n83\nfizz\nbuzz\n86\nfizz\n88\n89\nfizzbuzz\n91\n92\nfizz\n94\nbuzz\nfizz\n97\n98\nfizz\nbuzz\n" at /c/Users/saito/Documents/fast-fizzbuzz.scm:22:11
6 (tail-call 1) at /c/Users/saito/Documents/fast-fizzbuzz.scm:22:2
構築済みの文字列を表示するだけになっていることがわかる。
Document ID: e2741c0cea07aa2fbddc630e30240a15