* [2022-09-14 水]
((☼ . 晴れ) (☼☁ . 晴れ 時々 くもり) (☼☁ . 晴れ 時々 くもり))
** 空芯菜の収穫 :agriculture:
** common-lispでfizzbuzz :lisp:
とあるとこで見掛けたので、
とりあえず他の人のやり方とかは見ず
まっさらな心で書いてみた。
*** 自分の書いたfizzbuzz
#+BEGIN_SRC lisp
(defun fizzbuzz (n)
(loop :for i :from 1 :to n
:do (let ((lcm (lcm 3 5)))
(cond ((zerop (mod i lcm)) (print 'FizzBuzz))
((zerop (mod i 5)) (print 'Buzz))
((zerop (mod i 3)) (print 'Fizz))
(t (print i))))))
#+END_SRC
昔からよく見掛けるし、エンジニアの面接試験だかで
よく出てくるとか聞くけど、あんまりやった事ない気がする。
common lispはなんでもloopで書けるから楽。
でもlispらしくないと言えばらしくない。
他の人はどう書いているんだろう?と思ってネットを
調べてみる。
*** Common Lisp で Fizz Buzz
#+BEGIN_SRC lisp
(dotimes (i 100) (let (num) (setq num (+ i 1)) (if (= 0 (mod num 3)) (format t "Fizz") nil) (if (= 0 (mod num 5)) (format t "Buzz") nil) (if (and (not (= 0 (mod num 5))) (not (= 0 (mod num 3)))) (format t "~a" num)) (format t " " ))) (defun iota (counter &optional (start 0))
(if (= counter 0)
nil
(cons start (iota (- counter 1)
(+ start 1)))))
(defun fizz_buzz (num)
(cond ((and (= 0 (mod num 3)) (= 0 (mod num 5))) "FizzBuzz")
((= 0 (mod num 3)) "Fizz")
((= 0 (mod num 5)) "Buzz")
(t num)))
(format t
"~{~a ~}"
(mapcar #'fizz_buzz (iota 100 1)))
#+END_SRC
#1はdotimesを書いてて逆にその発想が最近薄くなっていると自分で
感じた。見た目はなんとなくlispっぽいんだけど、
ちょっとわかりづらい。他人の書いたソースコードだから
ってのもあるのかもだけど、if文を3つも使ったり、
その都度nilを入れてきたり、最後に隙間のformatを別途
使ったり、ちょっと冗長な感じもする。
ごめんなさい、批判したい訳じゃなく
あくまで他人のコードを参考に学ぶつもりで書いてます。
まぁ、素人って自分で書いているし、
素人で動くもの書けたんだから凄いと思う。
andを使ってるのもなるほどそういう書き方もあるなと感じた。
#2でiotaを作っているのは自分もちょっと考えた。
数列を作る時はiotaみたいな関数があれば便利かなと。
ただloopの場合、もうそういうものも要らなくなるから
敢えて作らなかった。
iotaを再帰で書いてるけど、lispっぽいし、
うまいなぁと。loopを多用している事もあって、
再帰的な考えがちょっと難しくなってると反省。
頭悪いからパッと理解出来ないんだよな笑
おお!しかもこっちはバシッとcondでまとめてきた。
#1は何だったんだ?って感じの書きっぷりだよな笑
ただ、最後はmapcarした後、
formatでループをかけてる。2回回してる。
iotaも入れると3回。これは遅くならないかな?
問題的にはどうなんだろ?括弧付きでいいなら
mapcarまででもいいような気もするんだけど。
きちんと出力するところまでやるなら、
やっぱりfizzbuzz関数内で出力した方がいい気がした。
次見つけたのはこれ。
*** Common Lispで最速のfizzbuzzを実装した話
#+BEGIN_SRC lisp
(defmacro fizzbuzz (n)
(format nil "~{~a ~}" (loop for i from 1 below n
collect (cond
((= 0 (mod i 15)) "fizzbuzz")
((= 0 (mod i 5)) "buzz")
((= 0 (mod i 3)) "fizz")
(t i)))))
(defun main ()
(print (fizzbuzz 100)))
#+END_SRC
これもloopやった後にformatでまたループやってるなぁ。
ただよく見るとdefmacro使ってるな笑
正直速度はあまり拘って作ってはいないけど、
ちょっと自分の書いたものと処理速度比べてみるか。
結果は以下の通り。
*** それぞれの速度
;; 自分の書いたもの
Evaluation took:
0.000 seconds of real time
0.000042 seconds of total run time (0.000036 user, 0.000006 system)
100.00% CPU
147,778 processor cycles
0 bytes consed
;; defmacroバージョン
Evaluation took:
0.000 seconds of real time
0.000009 seconds of total run time (0.000008 user, 0.000001 system)
100.00% CPU
29,156 processor cycles
0 bytes consed
確かに速いし、負けてる。
;; ちなみに先程のiotaバージョンは
Evaluation took:
0.000 seconds of real time
0.000043 seconds of total run time (0.000043 user, 0.000000 system)
100.00% CPU
153,587 processor cycles
0 bytes consed
結果自分のものとさほど大差なし。
自分の書いたものをただ単純にdefmacroにしてみた。
結果は以下の通り。早っ!
Evaluation took:
0.000 seconds of real time
0.000001 seconds of total run time (0.000001 user, 0.000000 system)
100.00% CPU
1,813 processor cycles
0 bytes consed
やはりprocessor cyclesが違うね。
速さに拘るんだったらlcmも消すな。
で、得意じゃないけど、最適化とかするだろうな。
で、やってみた結果がこれ
Evaluation took:
0.000 seconds of real time
0.000002 seconds of total run time (0.000002 user, 0.000000 system)
100.00% CPU
1,628 processor cycles
0 bytes consed
うーん、processor cyclesは下がったけど、
run timeは増えている。
もうこのレベルだといじったところで
大差ない。
逆にdefunで最適化したらどうなるんだろう?
型の最適化追加
Evaluation took:
0.000 seconds of real time
0.000049 seconds of total run time (0.000049 user, 0.000000 system)
100.00% CPU
177,785 processor cycles
0 bytes consed
あれ?最適化前より増えてるぞ笑
optimizeも入れてみた。
Evaluation took:
0.000 seconds of real time
0.000045 seconds of total run time (0.000037 user, 0.000008 system)
100.00% CPU
158,064 processor cycles
0 bytes consed
そんな速くなる訳でもないなぁ。
とりあえずここでは効果なしだな。
*** defmacro効果が凄い。
ただなんだろう。自分も一時期defmacro書きまくっていたけど、
defmacroを更にdefmacroしたりしてたらエラーが出て、
lisperの方に訊いたら、defunで書けるものは
なるべくdefunで書くように言われた。
それからはどうしてもdefunで書けないところだけに絞るようにしてる。
でも勉強になったなぁ。
まだまだ検索に引っかかってきたけど、もうお腹いっぱいなので、
やめておく。
いや、リアルの方はむしろお腹空いたけど笑
追記:過去の自分の練習した内容をgrepしてみたら、
2019-0322-201724.lispというのが見つかって、
そこで例の最速のfizzbuzzを一度自分で試してみてたようだ。
defmacroだとcompile時に計算して、
実行時はその計算結果を書き出すだけって自分で書いてある。
なるほど、それで速いわけだ笑