ワンライナーで Fizz Buzz
Fizz Buzz をワンライナーで書いてみます。ただし、ワンライナーといっても awk などを使ってしまえば普通のプログラミング言語で書くのと変わりませんので、そのような言語を使わず、また、シェルの for や while, if といった制御構造も使わないという制限を課してみました。
Fizz Buzz 問題については Wikipedia などに説明があります。簡単なプログラミングの問題です。
Fizz Buzz - Wikipedia
まず、1 から 100 の連番を作成します。これは seq コマンドを使うのが簡単です。出力をリダイレクトして 1.txt に保存しておきます*1。
$ seq 1 100 >1.txt
次のようなファイルが作成されます。1 から 100 まで全体で 100 行のテキストファイルになります。
$ head -n 5 1.txt 1 2 3 4 5
次に、3 の倍数を作成します。以下のように実行すると、3 から開始して 3 刻みで 100 までの数列が生成されます。これもリダイレクトして 3.txt に保存します。
$ seq 3 3 100 >3.txt
次のように、3 の倍数が列挙されたファイルが作成されます。引数に指定した 100 は 3 の倍数ではないので、最終行の値は 99 になります。
$ head -n 5 3.txt 3 6 9 12 15 $ tail -n 3 3.txt 93 96 99
同様に 5 の倍数も作成します。
$ seq 5 5 100 >5.txt
3 の倍数のときには Fizz と出力したいので、3.txt の各行に文字列 Fizz を追加します。
$ yes Fizz | head -n 100 | paste 3.txt - | grep ^[0-9] >3_fizz.txt
先頭の yes Fizz は、指定された文字列 Fizz を永久に出力し続けるコマンドです*2。これを head コマンドに渡すことで、各行に文字列 Fizz が書かれた 100 行の出力が得られます。この出力を paste コマンドで先ほどの 3.txt と貼り合わせて、最後の grep で余分な Fizz を取り除きます*3。結果は以下のようになります。
$ head -n 5 3_fizz.txt 3 Fizz 6 Fizz 9 Fizz 12 Fizz 15 Fizz
5.txt からも同様に 5_buzz.txt を作成します。
$ yes Buzz | head -n 100 | paste 5.txt - | grep ^[0-9] >5_buzz.txt
ここから join コマンドを使って Fizz Buzz の出力を作成します。join コマンドは、入力ファイルが文字列順にソートされていることを前提とするので、まず、各ファイルをソートします。
$ sort 1.txt >1_sorted.txt $ sort 3_fizz.txt >3_fizz_sorted.txt $ sort 5_buzz.txt >5_buzz_sorted.txt
文字列順にソートすると、たとえば 5_buzz_sorted.txt は次のようになります。
$ head 5_buzz_sorted.txt 10 Buzz 100 Buzz 15 Buzz 20 Buzz 25 Buzz 30 Buzz 35 Buzz 40 Buzz 45 Buzz 5 Buzz
join は、二つのファイルの中で共通のフィールドを持つ行を結合するコマンドです。たとえば、3_fizz_sorted.txt と 5_fizz_sorted.txt を join すると、両方のファイルに含まれる行が次のように出力されます。それぞれのファイルの何番目の列を結合に利用するかは、-1, -2 オプションで指定できます。指定されなければ先頭列が利用されます。
$ join 3_fizz_sorted.txt 5_buzz_sorted.txt 15 Fizz Buzz 30 Fizz Buzz 45 Fizz Buzz 60 Fizz Buzz 75 Fizz Buzz 90 Fizz Buzz
join コマンドに -a オプションを指定することで、結合されなかった行も出力に含めることができます*4。今回は、-a オプションを指定することで、1_sorted.txt に 3_fizz_sorted.txt と 5_buzz_sorted.txt を付け足す形で Fizz Buzz の出力を組み立てます。
$ join -a1 1_sorted.txt 3_fizz_sorted.txt | join -a1 - 5_buzz_sorted.txt >fizz_buzz.txt
出力結果はこのようになります。
$ head fizz_buzz.txt 1 10 Buzz 100 Buzz 11 12 Fizz 13 14 15 Fizz Buzz 16 17
これを sort コマンドで数値順に戻します。また、Fizz Buzz 問題では、数字の代わりに Fizz または Buzz を出力するということなので、不要な数字を除去します。
$ sort -n fizz_buzz.txt | sed 's/^[0-9]* //' >fizz_buzz_answer.txt
最終的に以下のような出力が正しく得られました*5。
$ cat fizz_buzz_answer.txt 1 2 Fizz 4 Buzz ... Fizz Buzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz
ここまでの説明では、一つ一つの処理結果をファイルに出力していましたが、bash のプロセス置換機能を使うと、全体の処理を以下のように書き下すことができます。
$ join -a1 <(seq 1 100 | sort) \ <(yes Fizz | head -n 100 | paste <(seq 3 3 100) - | grep ^[0-9] | sort) |\ join -a1 - \ <(yes Buzz | head -n 100 | paste <(seq 5 5 100) - | grep ^[0-9] | sort) |\ sort -n |\ sed 's/[0-9]* //'