J 言語なら 40 byte で FizzBuzz できる


J言語でFizzBuzzを作る という記事でコメントしながら、もっと短く書ける方法を思いついたので紹介します。

FizzBuzz (40 byte)

echo@(, ::]Fizz`Buzz;@#~0=3 5&|)@>:i.100

ちょっと読みにくいですね(J 言語のシンタックスハイライト欲しい)。適宜空白を入れてみましょう。

echo@(, ::] Fizz`Buzz ;@#~ 0 = 3 5&|)@>: i.100

あまり変わらないので 優先順位を考慮して括弧を入れます。

((echo @ ((, :: ]) ((Fizz ` Buzz) ((; @ #) ~) (0 = (3 5 & |))))) @ >:) (i. 100)

余計に読みにくくなってしまいました。とりあえず分解して解説します。

fizzbuzz=. , ::] (Fizz`Buzz ;@#~ 0 = 3 5&|)
echo@fizzbuzz@>: i.100

解説

echo@fizzbuzz@>: i.100

i.100
0 から 99 までの整数のリストです。

echo@fizzbuzz@>:
それぞれの整数を、インクリメント→変換→出力 します。@を使っているのは途中で結果が集められるのを防ぐためです。


fizzbuzz=. , ::] (Fizz`Buzz ;@#~ 0 = 3 5&|)

3 5&|
引数を (3 で割った余り , 5 で割った余り) のリストを作ります。& は、引数を部分適用します。

0 = 3 5&|
(3 で割り切れるか , 5 で割り切れるか) のリストを返すフォークです。

Fizz`Buzz
('Fizz' ; 'Buzz') と同じで、ボックスのリストです。文字数削減のために `悪用使用しています。

;@#~
~ は、右辺と左辺を入れ替える役割をしています。
(0 = 3 5&|) ;@# ('Fizz' ; 'Buzz')"_ と同じ結果になります。
# は、リストをフィルターするのに使っています。
3 で割り切れるときは <'Fizz' が、5 で割り切れるときは <'Buzz' が残ります。
最後に ; でボックスを開いて連結します。結果は 'Fizz' , 'Buzz' , 'FizzBuzz' , 空文字列 のいずれかになります。

, ::]
:: は、conjunction (接続詞)の一種で、, がエラーになれば代わりに ] が実行されます。

, ::] (Fizz`Buzz ;@#~ 0 = 3 5&|)
これはフックで、, ::] の引数は、左辺が元の整数、右辺が上で計算した文字列になります。
文字列が空の場合、, は正常に左辺の整数を要素に持つリストを返します。
そうでない場合、domain error が発生して、代わりに ] が右辺の文字列を返します。
この結果が最終的に echo に渡されます。


コードで表現すると ↓ のようになります(この方が分かりやすいかもしれません)。

isDevisibleBy3_5=. 0 = 3 5&|

boxedFizzBuzz=. Fizz`Buzz

filterAndJoin=. ;@#

replaceEmpty=. , ::]

fizzbuzz=. replaceEmpty (boxedFizzBuzz filterAndJoin~ isDevisibleBy3_5)

echo@fizzbuzz@>: i.100

参考

NuVoc
↑ とりあえず分からないときはこれで調べるといいと思います。
プリミティブの一覧の他に Ancillary Pages が結構役に立ちます。


日本語の情報が少ない or 古いので、いつか J 言語の布教入門記事を書きたいと思います(実行するかどうかは別)。