LoginSignup
5
3

More than 3 years have passed since last update.

J言語でライフゲームを作る

Posted at

はじめに

今回もJ言語です。
APLをJ言語になおして、ライフゲームを作ります。

J言語

J言語ってなんやねん

数式を短く単純に記述できる、スマートなプログラミング言語です。
様々なASCIIの記号の組み合わせにそれぞれ役割が割り当てられていて、とても使いやすいです。


例えば、下の式をプログラムで計算するとき……

4\sum_{k=1}^{10000}\frac{(-1)^{k-1}}{2k-1}
  • C++ の場合
double sum = 0;
for (int k = 1; k <= 10000; k++)
    sum += (k%2 ? 1. : -1.) / (2*k-1);
std::cout << 4*sum << std::endl;

for文で愚直に書くとできます。

  • Python の場合
4*sum([(1 if k%2 else -1) / (2*k-1) for k in range(1, 10000)])

Pythonなら一行で書けます。っょぃ。

  • J の場合
4*-/%1+2*i.1e5

比べ物になりません。
変数も制御構文もなしに計算できます。
J言語は圧倒的に短く書くことができるのです!

J902がリリース!

ついにJ言語の新バージョン、J902がリリースされました!

やはり、いちばん熱いのはdirect definition{{ }}ではないでしょうか。
今回の記事でさっそく使います。

VerbでもConjunctionでも何でも定義できる、便利な明示的な定義の構文です。
波括弧を使うという点では、一般的な言語と近くなり、親しみやすくなるのではないでしょうか。

J901
random=: 1 : '(m # i.#m) {~ ?@(+/m)'

kuji=: 3 : 0
fortune=. <;._1'|大吉|吉|中吉|小吉|凶'
rand=. 2 4 5 3 1 random
for_i. 1+i.y do.
  r=. rand''
  echo (":i) , '回目: ' , >r{fortune
end.
)
J902
random=: {{ (m # i.#m) {~ ?@(+/m) }}

kuji=: {{
  fortune=. <;._1'|大吉|吉|中吉|小吉|凶'
  rand=. 2 4 5 3 1 random
  for_i. 1+i.y do.
    r=. rand''
    echo (":i) , '回目: ' , >r{fortune
  end.
}}

シンタックスハイライトはまだ対応していませんね。
誰か対応させてくれないかなー(他人任せ)。

ライフゲーム

ライフゲームは生命のプロセスを再現したシミュレーションゲームです。
2次元グリッドに「生」のセルと「死」のセルがあり、周囲の8セルに生きているセルがちょうど3つあれば次の世代が生まれ、2つか3つなら生き残り、1つ以下(過疎)や4つ以上(過密)なら次の世代で死滅するといった単純なルールで進んでいきます。

詳しく知りたい方はWikipediaを見てください。

ライフゲーム - Wikipedia

本題

APL のライフゲーム

ある日、APLのWikiをながめてたら、APL(Dyalog APL)で作られたライフゲームを見つけました。

John Scholes' Conway's Game of Life - APL Wiki

Life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

おぉ……、なんだこれは。
やはり暗号度はJよりAPLのほうが高い気がします。

使ってみる

APL Wikiのグライダーの例を見ながら、TryAPLで使ってみます。

実行画面
      Life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}
      grid←¯5 ¯5↑3 3⍴1 1 1 1 0 0 0 1 0
      grid
0 0 0 0 0
0 0 0 0 0
0 0 1 1 1
0 0 1 0 0
0 0 0 1 0
      Life grid
0 0 0 0 0
0 0 0 1 0
0 0 1 1 0
0 0 1 0 1
0 0 0 0 0
      Show←{'□■'[⎕IO+⍵]}
      Show grid
□□□□□
□□□□□
□□■■■
□□■□□
□□□■□
      Show¨{Life⍣⍵⊢grid}¨0,⍳9
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□■□□│□■■□□│□■■□□│■■■□□│■■□□□│
│□□□□□│□□□■□│□□■■□│□□■■□│□■■■□│□■■□□│□■□■□│■■□□□│■□□□□│■□■□□│
│□□■■■│□□■■□│□□■□■│□■■□□│□■□□□│□■□■□│□■□□□│□□■□□│□■□□□│□□□□□│
│□□■□□│□□■□■│□□■□□│□□□■□│□□■□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│
│□□□■□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□■□□□│
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

ちゃんとライフゲームしてますね。

今回はJ言語でこれをします。
J言語はAPLの後継の言語なので、APLで出来ることはJでもだいたい出来ます。
文法もだいたい同じです。

APLとJの演算子の対応

まず、演算子の対応を調べました。
私はAPLには詳しくないので、間違ってたらごめんなさい。

       APL                    J             役割
{ 式 } {{ 式 }} 関数(動詞)の定義
y 引数
< ボックスに入れる
|."1 横方向に回転
∘.演算子 動詞/ 引数のそれぞれの組み合わせを計算
¯1 0 1 _1 0 1 { -1, 0, 1 } の配列
|. 縦方向に回転
, , リスト化(一次元配列化)
+ + 足し算
+/ +/ 総和
= = 等しい要素を判断
3 4 3 4 { 3, 4 } の配列
*. 論理AND
. . 内積
+. 論理OR
∨.∧ +./ .*. ANDしたもの同士をOR
1 ⍵ 1;y 1と引数をボックスに入れて結合
; ボックスから出していい感じにする
=: 名前の定義
Life Life 名前

置き換える

それぞれの演算子を置き換えて、J言語にします。

Life=:{{;(1;y)+./ .*.3 4=+/,_1 0 1|./_1 0 1|."1/<y}}

しかし、単純に置き換えただけではダメです。

実行画面
   Life=:{{;(1;y)+./ .*.3 4=+/,_1 0 1|./_1 0 1|."1/<y}}
   grid=:_5 _5{.3 3$1 1 1 1 0 0 0 1 0
   grid
0 0 0 0 0
0 0 0 0 0
0 0 1 1 1
0 0 1 0 0
0 0 0 1 0
   Life grid
|domain error: Life
|   ;(1;y)    +./ .*.3 4=+/,_1 0 1|./_1 0 1|."1/<y

J言語はAPLの後継とはいえ若干の仕様の違い1があり、それを補うために適宜動詞に&.>を付ける必要があります。

Life=:{{;(1;y)+.&.>/ .(*.&.>)3 4=&.>+&.>/,_1 0 1|.&.>/_1 0 1|."1&.>/<y}}
実行画面
   Life=:{{;(1;y)+.&.>/ .(*.&.>)3 4=&.>+&.>/,_1 0 1|.&.>/_1 0 1|."1&.>/<y}}
   Life grid
0 0 0 0 0
0 0 0 1 0
0 0 1 1 0
0 0 1 0 1
0 0 0 0 0

ちゃんとライフゲームしました。
しかし、無理やりAPLをJ言語に変換してる部分もあるので、少しコードが長くなってしまいます。
なので、もっとJっぽく書いてみます。
J言語ならもっと短く、簡単にできるはず!

J のライフゲーム

できました。

life=:((*4=])+3=])[:+/(<:3 3#:i.9)|.]

だいぶ短くなりました。さすがJ言語!
基本的な手順は変えていませんが、Jが処理しやすい計算方法に変えることで、かなり短くできました。

解説

APLでは横方向の回転に、縦方向の回転にを使うそうですが、J言語では素晴らしいことにその2つの操作が|.だけでできます。
|.の左引数にリストを渡すことで、各方向に回転する量を指定できます。
つまり、上下の回転と左右の回転を同時にできるということです。
また、0や負の数を指定することもできます。

実行画面
   z=:i.5 5
   z
 0  1  2  3  4
 5  6  7  8  9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
   1 2|.z    NB. 上に1、左に2だけ回転
 7  8  9  5  6
12 13 14 10 11
17 18 19 15 16
22 23 24 20 21
 2  3  4  0  1
   0 _1|.z    NB. 右に1だけ回転
 4  0  1  2  3
 9  5  6  7  8
14 10 11 12 13
19 15 16 17 18
24 20 21 22 23

これで、右下↘、下↓、左下↙、右→、(回転なし、)左←、右上↗、上↑、左上↖にそれぞれ1だけ回転させたものを計算します。
ずらす数は3進数を使っていい感じに計算しています。

実行画面
   3 3#:i.9    NB. 3進法2桁で0から8まで
0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2
   <:3 3#:i.9    NB. デクリメント
_1 _1
_1  0
_1  1
 0 _1
 0  0
 0  1
 1 _1
 1  0
 1  1
   (<:3 3#:i.9)|.grid    NB. それぞれの方向にgridを回転
0 0 0 0 1
0 0 0 0 0
0 0 0 0 0
1 0 0 1 1
0 0 0 1 0

0 0 0 1 0
0 0 0 0 0
0 0 0 0 0
0 0 1 1 1
0 0 1 0 0

...(省略)...

0 0 0 0 0
0 0 1 1 1
0 0 1 0 0
0 0 0 1 0
0 0 0 0 0

0 0 0 0 0
0 1 1 1 0
0 1 0 0 0
0 0 1 0 0
0 0 0 0 0

そして、今計算したものをすべて足し合わせると、それぞれのセルとその周囲のセルを合わせた9つのセルに存在する生存セルの個数が求まります。

実行画面
   sum=:+/(<:3 3#:i.9)|.grid    NB. +/で総和を求める
   sum
0 0 1 1 1
1 1 2 3 2
1 2 3 4 2
1 2 4 5 3
0 1 2 2 1

これが[:+/(<:3 3#:i.9)|.]の部分です。
]は引数を表し、[:は文法上必要なものです。


ここからは、ライフゲームのルールにしたがって計算します。

ライフゲームで生きたセルが存在する条件は次の2つです。

  • 周囲の生きたセルがちょうど3つ(誕生)
  • そのセルが生きていて、周囲の生きたセルが2つか3つ(生存)

今回はそれぞれのセルとその周囲の9セルの生きたセルの個数を数えているので、それにあわせて条件を書き換えると次のようになります。

  • 9セル中に生きたセルが3つある
  • 9セル中に生きたセルが4つあり、そのセルが生きたセルである

つまり、生きたセルが3つなら次の世代はそれ以外の条件なしで生きたセルに、4つならそのセルを調べ、その世代で生きているなら次の世代も生きたセルになるということです。
また、この2つの条件に当てはまらないセルはすべて死滅します。

そこで、9セルの和が3であるセルを1(生きたセル)としたものと、9セルの和が4であるセルを1としたものを用意します。

実行画面
   3=sum
0 0 0 0 0
0 0 0 1 0
0 0 1 0 0
0 0 0 0 1
0 0 0 0 0
   4=sum
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0
0 0 1 0 0
0 0 0 0 0

9セルの和が4の場合はもとが生きたセルである必要があるので、最初のgridと掛け算2することで両方が1である要素のみを取り出します。

実行画面
   grid*4=sum
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0
0 0 1 0 0
0 0 0 0 0

J言語は右から読まれるので、4=sumが先に処理されることに注意してください。

あとはこれらを足し算したら完成です。
9セルの和が3でかつ4であるセルは絶対に存在しないので、論理OR演算ではなく足し算で大丈夫です。

実行画面
   (grid*4=sum)+3=sum
0 0 0 0 0
0 0 0 1 0
0 0 1 1 0
0 0 1 0 1
0 0 0 0 0

これが((*4=])+3=])の部分です。

これらの処理の組み合わせでライフゲームの処理を実現しています。
40字にも満たないコードの中に、これだけたくさんの処理が入っているのです!

使ってみる

完成したものを、APLでしたのと同じように使ってみます。

実行画面
   life=:((*4=])+3=])[:+/(<:3 3#:i.9)|.]
   show=:{ucp@'□■'
   show grid
□□□□□
□□□□□
□□■■■
□□■□□
□□□■□
   <"2 show life^:(<10)grid
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□■□□│□■■□□│□■■□□│■■■□□│■■□□□│
│□□□□□│□□□■□│□□■■□│□□■■□│□■■■□│□■■□□│□■□■□│■■□□□│■□□□□│■□■□□│
│□□■■■│□□■■□│□□■□■│□■■□□│□■□□□│□■□■□│□■□□□│□□■□□│□■□□□│□□□□□│
│□□■□□│□□■□■│□□■□□│□□□■□│□□■□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│
│□□□■□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□□□□□│□■□□□│
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

動かしてみるとこんな感じです。
Conways-Game-of-Life_J.gif

おわりに

今回は少しAPLを触りましたが、Jよりボックスが使いやすいと感じました。
演算子もただの謎の記号ではなく、処理内容を想像させるような記号が割り当てられている気がします。
×÷などの数学の記号がそのまま使えるというのは、APLの素晴らしい部分だと思います。

しかし、J言語も負けていません。

解説まで読んでいただいた方は、J言語の短いコードの中に多くの処理が詰め込まれているのがわかると思います。
APLから受け継いだ機能にたくさんの演算子や使いやすい文法が加わり、より短く、単純に、わかりやすく書くことができます。

J言語はとてもステキなプログラミング言語です。
皆さんもぜひJ言語を使ってみてください!

参考

Jsoftware
J Wiki
NuVoc - J Wiki
APL Wiki
John Scholes' Conway's Game of Life - APL Wiki
TryAPL
ライフゲーム - Wikipedia


  1. APLではボックスに入れたまま中身を計算できるみたいです。すごいですね。 

  2. 行列同士を掛け算していますが、行列の積を求めているのではなく、単に同じ位置の要素同士の掛け算です。 

5
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3