FIZZBUZZプログラムを1行で書いてみる

0 はじめに

 今年の1月からプログラムの勉強を始めたため、いわゆる「FIZZBUZZプログラム」にチャレンジし、そのときのことをブログにした。

 

hiroringo.hatenablog.com

 

 今回は前回のブログの続きである。

 

1 腕試しにチャレンジ

 前回のブログで、利用中の言語PERLとC++、勉強中の言語RUBYとPYTHONを用いて「FIZZBUZZプログラム」を書いた。

 その際、「ちゃんと動くプログラムをとにかく書く」ことを目標して、特に「縛り」は設けなかった。

 そして、その目的は達成した。

 

 ところで、ウィキペディアのFIZZBUZZ問題の項目には次の記載がある。

 

(以下、https://ja.wikipedia.org/wiki/Fizz_Buzzより引用)

実際に「制限時間2分以内」「剰余(%記号等)を用いない」「1行でできる限り短く(ワンライナー)」等の縛りでゲーム条件を満たすコード記述の腕試しをする者が続出した。

 

 前回のブログでは、この部分に軽く触れただけだった。

 しかし、この部分に踏み込まないのはもったいない。

 そこで、今回はこの「縛り」プレイにチャレンジする。

 

2 ワンライナーに挑戦する

 まずは「ワンライナー(1行でできるだけ短く)」に挑戦する。

 言語は使い慣れたPERLを用いる。

 PERLならプログラミング能力のない私でもできそうだからである。

 

 前回は「while」でループを制御したが、今回は「for」でループを制御する。

 また、ワンライナーにするためには3項演算子を用いる必要があるだろうから、これを使う。

 

 以上を考えをソースコードに反映させてみる。

 こんな感じかな。

 

(以下、ソースコード

for ($i=1;$i<101;$i+=1){(!($i%15))?print" FIZZBUZZ!! ,\n":*1?print" FIZZ! ,":*2?print" BUZZ! ,\n":print" $i ,"))}

ソースコード終了)

 

 文字数がカウントできるサイト( http://www1.odn.ne.jp/megukuma/count.htm )を使ってソースコードの文字数を調べたところ、129文字となった(スペース抜きで120文字)。

 また、ちゃんと動くかチェックする。

 

f:id:Hiroringo:20210304173230j:plain

ワンライナー縛りで作ったFIZZBUZZプログラム(PERL)の実行結果1

 ちゃんと動いたようだ。

 めでたしめでたし。

 

3 文字の最小化にチャレンジする

 ワンライナーが思ったよりも簡単にできたので、「文字数を減らす方法」を少しだけ考える。

 

 まず、過剰なカッコを外す。

 これで何文字か減らせた。

 

 また、改行位置を変える。

 前回は5の倍数毎に改行していたが、15の倍数毎の改行に変える。

 

 さらに、構造を変える。

 前回のプログラムのforループの中身を日本語にすると次のようになる。

 

(15の倍数だったら)?(「FIZZBUZZ」と書け):

((3の倍数だったら)?(「FIZZ」と書け):

((5の倍数だったら)?(「BUZZ」と書け):(数字を書け)))

 

 この構造だと「と書け」の部分が何度も現れている。

 よって、この重複を最初に括れば文字数が減らせる。

 

 以上3点を修正したソースコードは次のとおりである。

 

(以下、ソースコード

for($i=1;$i<101;$i+=1){print !($i%15)?" FIZZBUZZ!!,\n":(!($i%3)?" FIZZ!,":(!($i%5)?" BUZZ!,":" $i,"))}

ソースコード終了)

 

 実行結果はこんな感じ。

 

f:id:Hiroringo:20210304175739j:plain

ワンライナー縛りで作ったFIZZBUZZプログラム(PERL)の実行結果2

 ちゃんと動いた。

 また、文字数は129(スペース抜きで120)から102(97)となった。

 これなら成功(目的達成)と言っていいだろう。

 

 以上、ワンライナーに挑戦した。

 予想よりも簡単にできた。

 また、3項演算子を教科書以外の場所で初めて使ったが、予想より簡単に使えた。

 今後も3項演算子を使いこなしていきたい。

 

3 「剰余を用いない」にチャレンジする

 次に、「剰余(%記号等)を用いない」にチャレンジしてみる。

 

 最初に思いついた方法は再帰関数を用いた次の方法である。

 

(以下、ソースコード

for ( $i = 1 ; $i <= 100 ; $i += 1 ) { print &FBoutput($i) ; }

sub FBoutput {
    if ( $_[0] > 15 ) { return &FBoutput($_[0]-15) ; }
    elsif ( $_[0] == 15 ) { return " FIZZBUZZ!!,\n" ; }
    elsif ( $_[0] == 3 or $_[0] == 6 or $_[0] == 9 or $_[0] == 12) { return " FIZZ!," ; }
    elsif ( $_[0] == 5 or $_[0] == 10 ) { return " BUZZ!," ; }
    else { return " $i," ; } }

ソースコード終了)

 

 関数(サブルーチン)を作り、ループの中をすっきりさせた

ワンライナーにこだわる必要がないので、多少長くなっても問題ない)。

 この関数の部分を日本語に変換すると次のとおりになる。

 

①16以上だったら15を引き、関数の最初の部分に戻す

②15以下で15だったら「FIZZBUZZ!!」を返す

③3、6、9、12だったら「FIZZ!」を返す

④5、10だったら「BUZZ!」を返す

⑤それ以なら数値を返す

 

 このプログラムでは剰余を用いていない。

 また、この関数はどんな数字を入力しても対応できる。

 そして、ちゃんと動く(実行画面は省略)。

 これなら、一応「ミッションクリア」と言えるだろう。

 

 この点、この関数には複数のreturnがある。

 だから、3項演算子を使って文字数・行数を減らすことができる。

 しかし、今回は文字数の縛りがない。

 また、3項演算子を使うとかえって分かりにくくなりそうである。

 よって、3項演算子は使わないし、その形に修正することもしない。

 

 また、今回は再帰関数を用いた。

 しかし、次のソースコードのようにwhileを使う方法もありそうである。

 

(以下、ソースコード

for ( $i = 1 ; $i <= 100 ; $i += 1 ) { print &FBoutput($i) ; }

sub FBoutput {
    $j = $_[0] ;
    while ( true ) {
        if ( $j > 15 ) { $j -= 15 ; }
        elsif ( $j == 15 ) { return " FIZZBUZZ!!,\n" ; }
        elsif ( $j == 3 or $j == 6 or $j == 9 or $j == 12) { return " FIZZ!," ; }
        elsif ( $j == 5 or $j == 10 ) { return " BUZZ!," ; }
        else { return " $i," ; } } }

ソースコード終了)

 

 どっちがいいかはよくわからない。

 まあ、両方とも書けて、かつ、動いたので、「両方正解」でいいかな。

 

 以上、「剰余を用いない」という条件もクリアできた。

 他にも面白い縛りがあればやってみたい。

 

 また、今回はPERLを用いたが、「練習」という点を考慮すれば、RUBYやPYTHONでもチャレンジすべきかもしれない。

 特に、「①剰余は使わない。②if等を用いず3項演算子を用いる。③再帰関数を必ず用いる。」という条件で書くというのは練習になりそうである。

 このブログを公開した後にでも挑戦しよう。

 そもそもRUBYやPYTHONについては「特に何かを参照することなくプログラムを作成できるようになる」という目標もあることだし。

*1:!($i%3

*2:!($i%5