コードゴルフ(素数を数えて落ち着くんだ)


あるところ(http://d.hatena.ne.jp/atomoharu/20090420/1240227378)でCode Golfをやっていて、面白い課題がありました。


2から任意の数までの中でランダムに5個素数を表示し合計も出す。 5個の中に重複した素数があってもよい。今回は100まで。プログラム中に100という数を書いていいが、1箇所の値を変えれば他の数にも対応できるようでなくてはならない。出力例は以下のとおり。

61,71,2,17,71
222

このサイトのコメントにpooqさんが、J言語で回答していていて、これがそれ。

   >( (,','&,)&":/;":&(+/))(?5##b){b=.(#~n&>)p:i.n=.100

きょうはこれを解析してみます。まず、何回か実験してみて結果を確認します。

   >( (,','&,)&":/;":&(+/))(?5##b){b=.(#~n&>)p:i.n=.100
53,83,23,11,83
253           
   >( (,','&,)&":/;":&(+/))(?5##b){b=.(#~n&>)p:i.n=.100
19,13,13,13,89
147           
   $ 53,83,23,11,83
5
   +/ 53,83,23,11,83
253
   +/ 19,13,13,13,89
147

普通、J言語で数字の配列はスペース区切りなのですが、カンマでも問題なく合計できたのは発見です。
全体眺めてみて、たぶん(?5##b){b=.(#~n&>)p:i.n=.100で100までの素数を5個選んでいるのだなぁ、とあたりをつけます。>( (,','&,)&":/;":&(+/))はいまのところ保留。

   i.20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   p:i.10
2 3 5 7 11 13 17 19 23 29
   p:0
2

i.(アイドット)という動詞は右側引数の数だけ整数を発生します。p:(ピーコロン)は右側引数番目の素数を返すようです。従って、p:i.n=.100では100個素数が作れてしまう。p:0が2なのは知らなかった。
次の(#~n&>)ですが、こういうのが考えにくいのですよねー。nは最初に100を入れているので、n&>は100&>という動詞。これを配列に作用させると、100より小さいと真(1)、同じか大きいと偽(0)という配列になります。この結果をもとの素数リストに#で作用させて100以下の素数の配列をつくろうとしている。
~(チルダ)は動詞の後ろにつけて左右の引数を逆転させる副詞ですので、結果的にこの(#~n&>)は動詞二つの並びから出来ている動詞(#~ n&>)です。すると(f g) y => y f (g y)ですので、yが100個の素数列、fが(#~)、gが(n&>)を代入してみます。

(f g) y => y f (g y)
(#~n&>)(100個の素数列)
=>(100個の素数列) (#~)( (n&>)(100個の素数列))
=>(100個の素数列) (#~)(100と比べた真偽列)
=>(100と比べた真偽列)(#)(100個の素数列) 
=>(100以下の素数列)
   (#~n&>)p:i.n=.100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
   # (#~n&>)p:i.n=.100
25

というわけで25個ありました。これをbに入れて、次に(?5##b){を作用させて、ランダムに5個選びます。

   b=:(#~n&>)p:i.n=.100
   #b
25
   5 # 25
25 25 25 25 25
   ? 25 25 25 25 25
8 0 15 5 19
   ? 25 25 25 25 25
21 9 15 21 9
   ? 5 # 25
4 8 0 22 24
   ? 5 # # b
15 21 7 2 9
   (?5##b) { b
43 61 3 61 47
   (?5##b) { b
53 67 47 37 71

#(シャープ)が右側にしか引数がない場合、単項動詞として配列の個数を数えます。左右に引数がある場合は、右側の引数を左側の引数回コピーします。?(クエスチョンマーク、はてな)は単項動詞として右側引数以下のランダムな整数を返します。{(左波かっこ、左ブレイス)は右側引数から左側引数で指定された位置の要素を返します。この結果、求める5個の素数が得られました。
だいぶ長くなってきましたが、もう少し!

   >( (,','&,)&":/;":&(+/))

数字としては、100以下の素数が5個選べたので、これに+/を作用させて合計を取れば回答になりますが、出題者の出力例がカンマ区切りなのでそれを考慮しています。以下の試行錯誤では一旦cという変数に5個の素数を入れています。

   c=:53 67 47 37 71
   >( (,','&,)&":/;":&(+/))c
53,67,47,37,71
275           
   (+/)c
275
   ":&(+/)c
275
   ( (,','&,)&":/)c
53,67,47,37,71
   ( (,'and'&,)&":/)c
53and67and47and37and71
   ( (,','&,)&":/;":&(+/))c
 +--------------+---+
 |53,67,47,37,71|275|
 +--------------+---+
   >( (,','&,)&":/;":&(+/))c
53,67,47,37,71
275

うむ、すごいですね。


えっと、言いたいのは一見暗号のようなAPL/J言語ですが、こうして解析してみるととても明晰というか、やりたいこと、やる方法、結果が表現されていてとてもすばらしいということです。


pooqさんすばらしい例をありがとうございました。