読者です 読者をやめる 読者になる 読者になる

Common LispでFizzBuzzを解いてみた

Ruby FizzBuzz最短コードメモ (51bytes) ネタバレ注意 - Qiitaを見て、Common Lispでは何byteで書けるかな?と思ったのでやってみた。
結論から言うと、あまり短いコードは書けなかった(技量的に)。しかしながら、面白いことを2つ発見したのでまとめておく。


負の剰余

Cで配列のインデックスを指定する場合や素数判定のプログラムを書くときに剰余演算子を用いたことはあった。そのときは正の剰余を想定して書いたので、負の剰余のことなぞ考えたこともなかった。しかし、上記の記事では負の剰余を用いており、ではCommon Lispではどうなるのかな?と思ったのでまとめてみた。処理系はCCL1.6。

n (mod n 15) (mod n -15) (mod -n 15) (mod -n -15)
1 1 -14 14 -1
2 2 -13 13 -2
3 3 -12 12 -3
4 4 -11 11 -4
5 5 -10 10 -5

rubyと同じようだ。他の言語では、どうなるだろう?

FizzBuzz

FizzBuzz問題を最短コードで書く場合"FizzBuzz"から、3の倍数なら"Fizz"を、5の倍数なら"Buzz"を, 15の倍数なら"FizzBuzz"を取り出すのが良いのではないかと考えた。そして、取り出すための関数として"subseq"を選んだ。すると以下のようになる。

(subseq "FizzBuzz" 0 4) => "Fizz"
(subseq "FizzBuzz" 4 8) => "Buzz"
(subseq "FizzBuzz" 0 8) => "FizzBuzz"

これを任意のnを与えてできないかと考えていたら、妙な数式が出来上がった。

(subseq "FizzBuzz" start end) 
start: (mod (mod (expt n 4) 15) 9)        ; exp(n,4) % 15 % 9 
end  : (+ (mod (mod (expt n 4) 15) -5) 8) ; exp(n,4) % 15 % -5 + 8

これを使うと、以下の様になる。

n 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
start 4 4 0 4 4 0 4 4 0 4 4 0 4 4 0
end 4 4 4 4 8 4 4 4 4 8 4 4 4 4 8
sebseq "" "" "Fizz" "" "Buzz" "Fizz" "" "" "Fizz" "Buzz" "" "Fizz" "" "" "FizzBuzz"

ほぼ数字遊びのような感じだが、うまく動くので何らかの数学的要因があるかもしれない。
しかも、これが面白いのは、3でも5でも15の倍数でもない値のときは""となるところだ。
だが、これをいざコードにすると、計算式が長い。また、3でも5でも15の倍数でもない値のとき、その数を返すわけではないのではないので、その値を表示させるには、さらに一工夫必要で、結果としてコードが長くなる。残念。


当初の目的であった、Common Lispで最短でFizzBuzzを解くというのは達成できなかったが、なかなか面白い発見はできた。Common Lispで最短FizzBuzzはひとまず止めるが、Common Lispの技量が上がったらまた解いてみようかな。また、新しい言語の取得の一環として最短でFizzBuzzを解くというのは、良い訓練ではないかと思った。

Common Lispで初めて実装

夏休みに入ってからM.Hiroi's Home Page / xyzzy Lisp ProgrammingとPAIPでCommon Lispの勉強をしている。
基本的には例題のコードを写して、実際に動かすといった具合だ。

夏休みも終わるなーと思っていた先日、「もし大学4年間が24時間だったら、現在は何時何分だろうか」と思った。
プログラムを書こうと思ったときに頭に浮かぶものはC言語C言語以外でプログラムについて考えたいと思って、Common Lispを始めたので、
「いかん、いかん、Common Lispで考えろ!」と自分に言い聞かせながら、一通り書いてみた。

(defconstant *hour* 24 "A day is 24 hours")
(defconstant *minute* 60 "A hour is 60 minutes")
(defconstant *second* 60 "A minute is 60 seconds")
(defconstant *day* 365 "A year is 365 days")
(defconstant *school-year* 4 "University is 4 years") 
(defvar *each-month-days* '(30 31 30 31 31 30 31 30 31 31 28 31) "Days of each months(April start)")

(defun StudentDays (year date day)
       "Days of student life"
	   (let ((days (* 365 (1- year)))
		 (date2 (mod (+ date 8) 12)))
	     (if (or (> day (elt *each-month-days* date2)) (< year 1) (> year 4)) (return-from StudentDays -1))
	     (dotimes (i date2 days)
	       (setf days (+ days (elt *each-month-days* i))))
	     (setf days (+ days day))))

(defun PrintTime (second)
       "Print 'hour:minute:second'"
           (if (< second 0)
               (format nil "error")
	       (progn (format nil "~s:~s:~,2F" (truncate (/ second 3600)) (truncate (mod (/ second 60) 60)) (float (mod second 60))))))

(defun StudenTtime (year data day)
       "Total seconds of student life"
       (let ((seconds (/ (* *hour* *minute* *second*) (* *day* *school-year*))))
	     (printtime
	      (* seconds (studentdays year data day)))))
(StudentTime 3 10 3)
"15:6:24.66"
(StudentTime 3 3 3)
"17:32:23.01"
(StudentTime 5 10 3)
"error"
(StudentTime 5 10 32)
"error"

しかし、初めて関数型言語で書いたので、関数型言語のスタイルが分からない。Common Lispも多くのコードに触れていないので、Common Lispのスタイルも分からない。そこで、今後のために現在の疑問を挙げようと思う。

・大域変数は"*変数名*"というスタイルであるが、定数の場合はどうなるのであろう?(今回はとりあえず、大域変数と同じスタイルにした。)
=>アスタリスクは付けないようだ。(2015/10/02)

スペシャル変数の名前は、最初と最後にアスタリスクを付けるのが慣習だが、定数の場合は通常この慣習に従わない。理由は、アスタリスクを付けると、「要注意!このレキシカルスコープの外側で何かによって変更されるかもしれない」と解釈されるからだ。もちろん、定数は変更されない。


Peter Norvig(2010) 「実用Common Lsip」翔泳社 149pp.


・計算結果を出力する際にPrintTimeで出力をしたが(1)、PrintTimeでは出力文字列を返すだけにして、呼び出し元(StudentTime)で表示するべきか(2)?

(1)

(defun foo ()
  (hoge))

(defun hoge ()
  (format nil "hoge"))

(2)

(defun foo ()
  (format nil "~S" (hoge)))

(defun hoge ()
  'print)

・Lispboxでは日本語が対応していないのでコメントも英語で書いてみたが、正しいか分からない。コメントの英語が正しいかチェツクする方法はあるのか?(とりあえずは英語のコメントにたくさん触れ、その中で身に付けていこうかな)


C言語取得の際は多くのコードに触れたことと例題のコードばかり写してないで、自分で一から創り上げたことが良かったと思うから、Common Lispでもそうしたい。また、人工知能にも興味があるから、その辺を絡めていけばよいのではないかと思っている。

ブログを始めるにあたって

プログラミングを始めて2年が経った。大学で情報系の学部に進学して授業で習い始めたのがきっかけだった。
それまで、全くプログラミングに触れたことはなかったし、周りにプログラミングをする人もいなく、C言語という言葉も大学に入ってから知ったくらいプログラミングはかけ離れた世界のものであった。

もちろん始めたころは苦労した。どうやって動いているかも謎だった。ようやく、半年過ぎたあたりから授業の問題が簡単に思えてきた。しかし、そのころはC言語で文字列処理や数値計算ばかりで自分が想像していたプログラミング(アプリとかweb系とか)とは違うものであり、授業でプログラミングを学んで何が創れるのだろう?と疑問に思った。

それから、授業以外でもプログラミングを勉強するようになり、いろいろ創ったり、プログラミングコンテストの問題を解いたりした。そこでたくさんのプログラムを書く中で、たくさんのエラー出し、たくさんのエラー内容を確認した。

エラー内容が分からずに検索したそんな時、行きつくのは、過去に同じエラーをだして、その解決法を載せているたくさんのブログだった。そこには有益なことが書いてあり、たくさんの知識をいただき大変お世話になった。

そのおかげで、始めたころからは想像もつかないくらいに上達した。


そして、2年が経過して思うことは、これまでに取得してきた知識を可視化したら、膨大な量になったであろうなってこと。また、自分がたくさんのブログにお世話になってきたように、自分のエラーが誰かの参考になれば嬉しいなってこと。


そこで、このブログを始めようと思った。

有益な情報を公開する

 これまたよいことは、役に立つおもしろい情報を集めて選り分け、それをWebページにしたり、あるいは FAQ (Frequently Asked Questions lists)のような文書にすることです。そしてそれらを一般公開することです。
How To Become A Hacker: Japanese

果たして有益な情報であるか分からない(情報を集めて選び分けているわけではないので)が、一人の人間が時間をかけて悩んできたことがまとめられ、他人がそれを読んでより短い時間で解決できることは有益なことであると思う。