Diary

Diary

日々学んだことをアウトプットする場として初めてみました

FizzBuzz をワンライナーで倒す

FizzBuzz 問題とは、条件分岐の式をかくための例題としてプログラマーの間では有名な問題です(wiki)。

今回は初心に帰って FizzBuzz を、ワンライナーで解いてみたいと思います!

自分が思いついた解法は次の3つです

前提方針

対象となる数字一覧については seq を用いて連続生成することで、シェルコマンドは文字列操作に集中できるようにしています。

$ seq 5
1
2
3
4
5

以下の例では、100 までの数字について FizzBuzz 処理を実行しています。

awk

やっぱり、シェルで数値の条件分岐などをしようと思ったら AWK が楽かと思います。

まずは、愚直に3つのパターンに条件分岐します。

  1. 15 で割れるものは "FizzBuzz" と出力する。
  2. 1 に当てはまらないもののうち、3 で割れるものは "Fizz" と出力する。
  3. 1 に当てはまらないもののうち、5 で割れるものは "Buzz" と出力する。
$ seq 100 | awk '{if($1 % 15 == 0){print "FizzBuzz"}else if($1 % 3 == 0){print "Fizz"}else if($1 % 5 == 0){print "Buzz"}else{print $1}}'
1
2
Fizz
4
Buzz
...

出力は正しくできてますが、if, else if が続いて読みにくいです。

少し考え方を変えてみます。

15 の倍数の時の処理を 3 の倍数と 5 の倍数の足し算だと考えることで、「"FizzBuzz" = "Fizz" + "Buzz" 」のように出力することを目指します。

以下では awk連想配列に一時的に値を保存して、最終行が終わったときに実行される END というブロックで連想配列の結果を出力しています。具体的な方針は以下の通りです。

  1. 数値 i が3 の倍数であれば、連想配列の i のキーに "Fizz"を追加する
  2. 数値 i が5 の倍数であれば、連想配列の i のキーに "Buzz"を追加する
  3. 全ての数字のチェックが終わった後、連想配列の数値を出力する。その際、キー i の値が何もなければそのまま i を出力する。
seq 100 | awk '{if($1 % 3 == 0){a[$1] = "Fizz"}}{if($1 % 5 == 0){a[$1] = a[$1]"Buzz"}}END{for(i=1; i<length(a); i++){if(a[i] != ""){print a[i]}else{print i}}}'

sed

次は文字列を置換することで有名な sed を使って処理してみたいと思います。

素直なやり方

sed 余りなどという概念がないため、行数をカウントすることで何の倍数か判断させています。

  1. 5 の倍数の行数であれば、その数字を Buzz に置き換える
  2. 3 の倍数の行数の文字に対しては、以下の処理を行う 2.1 その行数の文字が数字であれば、Fizz に置き換える 2.2 その行数の文字が数字以外(="Buzz")であれば、その文字を残しつつ "Fizz" を先頭に追加する。
seq 100 | sed '5~5s@.*@Buzz@' | sed -E '3~3s@[0-9]*([\w]*)@Fizz\1@'
# よく考えないとわからないやり方
seq 100 | sed '5~5s@.*@Buzz@' | sed '3~3s@[0-9]*@Fizz@'

(おまけ)pythonワンライナーで使ってみる

python も -c オプションを使うことで、コマンドラインワンライナーで実行することができます。

Qiita の記事を参考にすると、以下が一番簡潔に書けそうです。

python3 -c 'for i in range(1,101):print("Fizz"*(i%3<1)+"Buzz"*(i%5<1)or str(i))'

おわりに

正規表現について少しずつナレッジが溜まってきたので、そのうちまとめてみたいと思います