J

J、その可読性の低さゆえ全く流行していないプログラミング言語。

まずはJをゲット

Jの処理系はJsoftwareで配布されている。
Download→Stableからそれぞれの環境にあった処理系のインストーラをダウンロード。

インストーラでは、一人で使うのか、それともPCのユーザー全員で使うのか聞いてくる。
これは人それぞれ自由に選んでおk。
一人で使う場合はそのユーザのホームフォルダに、全員で使う場合はProgram Files(Windowsの場合)にインストールされる。

Jに触ってみる

Jの起動

Jの処理系は、Jがインストールされたディレクトリから
./bin/j.exe
と辿ったところにあるので起動する。

Jの処理系は対話型なので、プログラムを入力するとその場で実行してくれる。
これを使ってJを高性能かつ簡単な電卓として使うこともできる。

Hello, world!

Jでは、文字列は''(シングルクォーテーション)で囲ったもので表される。
また、文字列を入力するとそのまま返すので、Hello, world!プログラムは以下のようになる。

'Hello, world!'

上の通りにJに入力してやると、Hello, world!という表示が返ってくる。
Jの窓は以下のような状態になっているはず。

   'Hello, world!'
Hello, world!

このように、Jでは入力を半角スペース三つ分インデントして表示する。
また出力は行頭から表示される。
以降に示すサンプルも全てこれに則って書いてあるので
実際に試すときはインデントされている部分だけを入力すればおk。

呼称について

例えば普通のプログラミング言語の場合、以下のプログラムでは「10」が定数、「+」が「演算子」、「printf」が「関数」、「a」が「変数」などと言った呼び方をする。

int a = 10;
printf("%d\n", a + 10)

だがJの場合、演算子とか関数とか言った呼称は用いず、次のような呼び方をする。

ほかにも、動詞の動作に影響を与える副詞、品詞同士をつなぐ接続詞などもある。
余談だがこのように自然言語っぽい呼び方をする所は俺大好き。

まずは簡単な計算から

四則演算+α

まず加算減算乗算除算そして剰余を求める計算をやってみる。
加算減算乗算を行う動詞はそのまんま、+と-と*。

   5 + 3
8
   7 - 4
3
   6 * 5
30

除算の動詞は % を使う。
普通の言語だと除算は / で、剰余が % とかいうことが多いけど、
Jでは除算が % なので間違ったりこんがらがったりしないように。

   16 % 8
2
   5 % 2
2.5
   13 % 7
1.85714

で、剰余(あまりを求める)の動詞は | (縦線)。
注意しなきゃいけないのは、剰余を求めようとして「x | y」とした時に、
「xをyで割ったときのあまり」ではなく「yをxで割ったときのあまり」になるから
名詞の順番を間違えないように。

   5 | 13
3
   2 | 30
0

とりあえず理解確認のために以下の問題を解いてみようか。

答がそれぞれ56088、5.76471、11になったら読み進めよう。
ならなかったら読み直せ。

数値リテラル

一口に数といってもいろいろある。
整数、小数、正の数、負の数、実数、複素数・・・
それらの数を表現するための方法が幾つかある。

指数表現

とても大きい数やとても小さい数を表すのに使う「x * 10のy乗」の形。
「x * 10のy乗」ということで、10を底とした指数表現になる。 たとえば「13000000000」は「1.3*10の10乗」で「1.3e10」と書く。

   1.3 * 10000000000
1.3e10
   1.2345e5
123450

負の数

負の数はふつう「-」(マイナス)の符号をつけるが
Jの場合は少し違う。Jで負数を表すには _ (アンダースコア)を使う。

   3 - 5
_2
   _3 + 9
6
   _5 * 4
_20

無限・未定義

数が大きすぎたり、定義されない値をとった場合の表現の仕方があるが
まあ普通、ほとんど使わない。
無限は _ (アンダースコア)、未定義は _. (アンダースコアとピリオド)で表される。 無限と負数の表現が同じだけれども、無限の場合はアンダースコアだけを書き、
負数の場合は数も書くので区別できる。
ちなみに、アンダースコアを二つ繋げるとマイナス無限大を表すこともできる。

   1.2345e999 * 6.789e999
_
   _. * 123 + 456 - 789 % 999
_.
   _1 * _
__

その他

その他にも複素数とか分数とかあるけどまあ後々説明する。

優先順位

優先順位は数学と同じ、なんて甘いことを言っちゃいかん。
だからといって複雑な順位が決められてるわけでもなく、
右から順番に計算されるだけ。つまり、

   1 - 2 - 3 - 4
_2

こうなる。
「1 - 2 - 3 - 4」は「1 - (2 - (3 - 4))」になるので答はマイナス2になる。

というわけで確認問題

予測と実際の答が合っていれば読み進めよう。
合ってなかったら読み直せ。

単項と二項

Jの動詞は単項で使った場合(動詞の右にだけ名詞がくる)と
二項で使った場合(動詞の左右両方に名詞がくる)場合で全く異なった挙動をする。
詳しい挙動については後々品詞一覧で述べようと思うが
代表的なものを挙げておこうと思う。

「*」は単項だと符号の判定、二項だと乗算になる。

   2 * 4
8
   3 * 5
15
   * 3
1
   * _12
_1
   * 0
0

「|」は単項だと絶対値、二項だと剰余になる。

   3 | 15
0
   4 | 22
2
   | 50
50
   | _13
13
   | 0
0

この単項と二項の使い分けはJのコードを読む上でとても重要だから
単項と二項で動作が変わるということは覚えておいてほしい。

コメント

コメントは「NB.」でつけることができる。 NB.以降は行末までがコメントになる。

   3 * 5 + 6  NB. 5と6を足して3倍する
33

アレイ

アレイの基本

Jでは沢山の値を纏めて扱うことができる。
他の言語でいう配列のようなものをアレイと呼ぶ。
アレイを定義するには、数値同士を半角空白で区切ればいい。
アレイ同士の演算も可能である。

   1 2 3 + 4 5 6
5 7 9
   1 2 3 * 6 5 4
6 10 12

アレイ同士で演算を行う場合、両方の長さが等しくなければならない。
長さの等しくないアレイ同士で演算を行おうとすると、length errorと怒られる。

   1 2 3 4 + 5 6
|length error
|   1 2 3 4    +5 6

また、アレイと単一の名詞との演算もできる。

   1 2 3 + 5
6 7 8
   1 2 3 * 10
10 20 30

アレイの操作

アレイの要素数は動詞 # (シャープ)で求めることができる。

   # 1 2 3
3
   # 1 2 3 4 5 6 7 8 9 10
10

0から指定された値 - 1までのアレイを作る動詞 i. (イオタ)というものもある。

   i. 10
0 1 2 3 4 5 6 7 8 9
   5 + i. 10
5 6 7 8 9 10 11 12 13 14
   # i. 10
10

アレイの整形

動詞 $ (ドル)を使うと、アレイを整形することができる。
整形する場合、$は二項動詞として用い、左に形、右に値をもってくる。
二次元以上のものを作り出すことができる。

   3 4 $ i. 12    NB. 3 * 4のテーブルの形に i. 12 を整形する
0 1  2  3
4 5  6  7
8 9 10 11
   2 4 $ 1 2 3    NB. 要素が足りない場合は繰り返される
1 2 3 1
2 3 1 2
   3 3 $ i.100    NB. 要素が余る場合は切り捨てられる
0 1 2
3 4 5
6 7 8

副詞を使ってみる

副詞は、動詞の動作に影響するもので、使いようによってはかなり使える。
とりあえず一番よく使うであろう副詞 / (スラッシュ)の説明をしよう。

まず副詞はどうやって使うかというと、動詞の直後につけて使う。
動詞 + に副詞 / をつけると +/ となる。
この場合、 +/ という新しい動詞ができたと考えた方が簡単。

で、この副詞 / というものの効果は「挿入」(もちろん性的な意味dうわなにをするやめr)。
「挿入」というのはアレイの各要素の間にその動詞を挿入することを言って、
「+/ 1 2 3 4 5」という文は「1 + 2 + 3 + 4 + 5」と解釈される。
つまり +/ というのはアレイの要素の合計を求める新しい動詞になるわけだ。
これを使えば、1~100までの自然数の和を求めるプログラムだって簡単に書ける。

   +/ i. 101
5050

フックとフォーク

さて、それではJ言語の一番の特徴である「動詞の合成」の説明をしようと思う。
ここは結構複雑だから一気に理解しようとせずに、ゆっくり理解していってほしい。

フック

フックは動詞二つを合成するときの決まりで、以下のようになる。
(f, gは動詞、x, yは名詞)

(f g) y   →   y f g y
x (f g) y →   x f g y

まず単項の場合、動詞gを単項動詞としてyを与えて評価させる。
そして動詞fを二項動詞として「g y」の値と、元のyを与えて評価させる。例えば

   (+ *) 5
6

この場合、まず動詞*に5が渡されて「* 5」が評価される。
単項*動詞は符号の判定なので、「* 5」は1となる。
次に動詞+に 1 と 5 が渡され、結果は6となる。

二項の場合、最後の動詞fに渡す名詞がyではなくxになるだけで、
単項の場合の動作が分かれば簡単に理解することができる。

フォーク

フォークは動詞三つを合成するときの決まりで、以下のようになる。
(f, g, hは動詞、x, yは名詞)

(f g h) y   →   (f y) g h y
x (f g h) y →   (x f y) g x h y

まず単項の場合、動詞fとhにyを与えて評価し、
その結果の二つ値をgに与えて評価させる。例えば

   (+/ % #) 3 5 7 9
6

この場合、

となるから、

(+/ 3 5 7 9) % # 3 5 7 9

と解釈される。

#はアレイの要素数を求めるので 4 (これが「h y」の値)

  1. /はアレイの要素の合計を求めるので 24 (これが「f y」の値)
    よって、
24 % 4

になるから、答は6となる。
合計を要素数で割る操作、つまりこれは平均を求めていることになる。
フォークをうまく使うことで、平均を求める操作を (+/ % #) だけで記述することができる。

トレイン

四つ以上の動詞の合成時の決まりで、これはフックとフォークの組み合わせにすぎない。 例えば、四つの動詞a, b, c, dを合成するときは

(a b c d) → (a (b c d))

となり、右から三つ b, c, d でフォークが起きる。
その結果と a でフックが起きる。

トレインの合成規則はかなり複雑になるが、所詮フックとフォークの合わせ技なので
フックとフォークを理解できていれば読むのは難しくはない。

代入と定義

Jでは、代入の操作を行うことによって、名詞や動詞などを定義することができる。
代入には二種類あって、 =. (ローカル)、 =: (グローバル) がある。

ローカル

ローカルな代入は、その範囲内でしか利用しない単語の定義に使う。
例えば以下の例では、名詞 a は動詞 test 内でしか利用できない。
(3 : 0の意味などについては後述する。)

   test =: 3 : 0    NB. 動詞の定義
a =. y
2 * a
)

グローバル

グローバルな代入は、あらゆる場所でその単語を利用することができる。
例えば以下の例では、動詞の定義外で定義された名詞を動詞の定義内で使用している。

   global =: 0
   test2 =: 3 : 0
global =: global + 1
)

名詞の定義

名詞を定義するのは簡単で、代入の動詞を使えばおk。

   a =. 5
   a         NB. a の値を表示する
5

すべての文は右から評価されるから、名詞の内容に文をもってきたら
それが評価されて、名詞に代入されることになる。

   b =. 5 + i. 10
   b
5 6 7 8 9 10 11 12 13 14

名詞どうしの演算もできるし、名詞を使った演算の結果を別の名詞に代入することもできる。

   c =. 10
   d =. i. 5
   c + d
10 11 12 13 14
   e =. c * d
   e
0 10 20 30 40

動詞の定義

動詞といっても、単項と二項があって
さらに二種類の定義方法が存在する。

暗黙(Tacit)の定義

暗黙の定義では引数といったものは存在せず、
ただ動詞の中身が展開されるだけである。
一番簡単な暗黙の定義を見てみよう。

   plus =: +

これを使うコードは例えば以下のようになる。

   10 plus 20
30

つまり、コードは以下のように展開されたことになる。

   10 plus 20   →    10 (+) 20

暗黙の定義は、括弧つきで展開されるのと同等なので
動詞の合成を効果的に使うことができる。

   avg =: +/ % #    NB. 平均を求める
   avg 10 15 22 34 42
24.6

このコードは以下のように展開されたことになる。

   avg 10 15 22 34 42   →   (+/ % #) 10 15 22 34 42

明示的(Explict)な定義

明示的な定義は、引数を受け取って値を返すという動作を明確に定義する方法である。 引数は単項か二項かによって数が変わるため、
定義する時点で単項動詞か二項動詞かを明示しておく必要がある。
(暗黙の定義は展開されるだけなので明示の必要はない。)

動詞の種類の明示には、接続詞 : (コロン)を使う。
: の左に動詞の種類、右に動詞の中身を文字列として記述する。
例えばさっきの加算を行う動詞 plus を明示的な定義で書き直すと以下のようになる。

   plus =: 4 : 'x + y'

まず、 4 というのが二項動詞の明示である。
で、動詞の左側に置かれる引数が x 、右側に置かれる引数が y という名前で予約されている。

次に、平均を求める動詞を明示的な定義で書き直してみる。

   avg =: 3 : '(+/ y) % # y'   NB. もちろん合成も使えるので '(+/ % #) y' としてもおk

さて、定義したい動詞が長いと、どうしても一行じゃ読みにくくなってしまう。
そこで、接続詞 : の右側に 0 を持ってくると複数行の動詞を定義することができる。

   test =: 3 : 0
y + y
)

複数行の動詞の定義は ) で終了する。

暗黙の定義と明示的な定義

上の二つの例(動詞 plus, avg)でも分かる通り、
明示的な定義の方が長く、冗長になりがちだが、暗黙の定義は読みにくい。
どちらを使うかは好みだろうが、明示的な定義の方が定義しやすいし、メンテナンスも容易である。
一方暗黙の定義はスマートで、こちらの方がJの特色をより反映していると言える。
なのでどちらを使ったほうがいいというものはないから、好みで使い分けておk。

ここでもう一つ、動詞の定義方法を紹介しようと思う。
でもまあ新しいわけではなく、明示的な定義を暗黙の定義に変換する方法だ。
明示的な定義を暗黙の定義に変換するには、接続詞 : の左側に 13 を置けばいい。

   fx =:  3 : '32 + y * 9 % 5'    NB. 摂氏を華氏に変換する
   ft =: 13 : '32 + y * 9 % 5'    NB. 摂氏を華氏に変換する(変換ver)

ここで動詞の中身を見てみると

   fx
3 : '32 + y * 9 % 5'
   ft
32 + 1.8 * ]

となり、ftの方が暗黙の定義に変換されているのが分かる。

さあプログラムを書こう

これまでで大体Jの基本は抑えられたと思う。
後は語彙の問題で、どんな単語があって、どんな動作をするのか。
これを覚えていけばいいだけ。

ただ、語彙はけっこう豊富だし、それも記号の羅列になるから記憶するのは容易ではない。
増してや勉強感覚で覚えようと思っても身につくわけがない。
だからここからは、ひたすらたくさんプログラムを書いて、
その中で語彙を増やしていってほしいと思う。

ちなみに単語の一覧表はJsoftwareのVocabularyにあるから
ブックマークに突っ込むなり何なりしておくと便利だと思う。 英語が読める人はJsoftwareからWikiを辿って色々な文章を読んでいくのも面白い。ちなみに俺は読めない。

Project Euler

さて、書くプログラムのお題だが、ここではJの得意な数学分野の問題、Project Eulerを解こうと思う。
Project Eulerの問題は英語で書かれているが、Project Euler - PukiWikiで和訳が掲載されているのでここを参照する。

Problem 1

10未満の自然数のうち、3 もしくは 5 の倍数になっているものは 3, 5, 6, 9 の4つがあり、 これらの合計は 23 になる。

同じようにして、1,000 未満の 3 か 5 の倍数になっている数字の合計を求めよ。

まず、1000未満の数ということなので、999までの数列を用意する。
これは前に説明した i. (イオタ)を使えば簡単にできる。
とりあえず数列を適当な名詞に代入しておく。

   t =. i. 1000

さて、次に3もしくは5の倍数になっている数を取り出して、その総和を求めたい。
ということはまず、どれが3もしくは5の倍数かを判断する必要がある。
ここではまず3の倍数から考えていく。

3の倍数ということは、3で割った余りが0になるということなので
動詞 | を使って余りを求めた結果が0ならば3の倍数ということになる。
比較の動詞には = (イコール)を使う。二項が等しければ1、等しくなければ0が返される。

   0 = 3 | t
1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 ...

次に、上のアレイの 1 に対応する部分だけを t から取り出せば3の倍数の数列が得られる。
ここではその為に動詞 # を二項動詞として使う。

二項動詞としての # は「コピー」の動作を行う。
左辺のアレイの要素の値ぶん、右辺の要素を繰り返す。まずは例を見てほしい。

   3 # 1    NB. 1 を 3回繰り返す
1 1 1
   1 2 3 # 4 5 6   NB. 4 5 6 をそれぞれ 1 2 3回繰り返す
4 5 5 6 6 6
   1 1 0 0 1 1 # 1 2 3 4 5 6   NB. 0 の部分は繰り返されない
1 2 5 6

これを使えば、 t から3の倍数だけを取り出すことができる。

   (0 = 3 | t) # t
0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 ...

同じようにして、 t から5の倍数だけを取り出すと

   (0 = 5 | t) # t 
0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 ...

残った作業は

  1. 二つのアレイを連結
  2. 重複する要素(つまり15の倍数)を一つにする
  3. 総和を計算する

となる。

まず、二つのアレイを連結するには動詞 , (カンマ)を使う。

   ((0 = 3 | t) # t) , (0 = 5 | t) # t
0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 ...

重複する要素を一つにするのは結構難しいと思うかもしれないが
幸運なことに、Jにはその作業を行ってくれる動詞 ~. がある。

   ~. ((0 = 3 | t) # t) , (0 = 5 | t) # t
0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 ...

後はこれをすべて加算すればおk。
副詞 / の力を借りて

   +/ ~. ((0 = 3 | t) # t) , (0 = 5 | t) # t
233168

纏めると

+/ ~. ((0 = 3 | t) # t) , (0 = 5 | t) # t =. i. 1000

また、ちょっと狡賢く頭を働かせれば、以下のようにも書ける。
ぜひ解読にチャレンジしてみてほしい。

+/ ~. (3 * i. 334) , 5 * i. 200

Problem 2

フィボナッチ数列の項は前の2つの項の和である。 最初の2項を 1, 2 とすれば、最初の10項は以下の通りである。

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

数列の項が400万を超えない範囲で、偶数の項の総和を求めよ。

Note:この問題は最近更新されました。お使いのパラメータが正しいかどうか確認してください。

よくあるフィボナッチ数列を使った問題。
数列をどんどん求めていくので、フィボナッチ数列の第n項が求められる動詞を定義すればよさそうだ。
ちょうどWikipediaにフィボナッチ数 - 一般項の項目があった。
そこにある式によると黄金比を使ってフィボナッチ数列の第n項の正確な整数値が求められるらしい。

というわけでまずは黄金比を定義してみる。
φ≡(1 + √5)/2 ということで、平方根を求める必要がある。
平方根は動詞 %: によって求めることができる。
また、二で割るということは、値を半分にするのと同じなので、値を半分にする動詞 -: を使う。

   phi =. -: 1 + %: 5
   phi
1.61803

次にこれを使ってフィボナッチ数列の第n項を求める動詞を書く。
ここで注意しなければならないのは、Wikipediaの式は第0項と第1項の値が0, 1と定義されているのに対し、
この問題の場合は 1, 2と定義されている。
二つの数列をよく見ると、問題の数列の第n項とWikipediaの数列の第n + 2項が等しいことがわかる。
つまり、引数よりも二つ右隣の項を計算すればいいことになる。
これをもとに動詞を定義すると

   fib =: 3 : '<. 0.5 + (phi ^ y + 2) % %: 5'

となる。未解説の動詞の説明をすると

Wikipediaの式と見比べて、解読に挑戦してほしい。

さて、これでフィボナッチ数は求められるようになった。
次は400万までの数列を作り出し、偶数だけを取り出し、総和を求める必要がある。
まず400万までの数列を作るには、適当な数まで数列を作っておいて
そこから400万に満たない数だけを取り出せばいい。取り出す作業はさっきと同じく # を使う

   f =. fib i.40    NB. 適当な数までのフィボナッチ数列
   (4e6 > f) # f
1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 ...

比較の動詞 > を使っている。これは見たまま「より大きいかどうか」を判定する動詞である。

次にそのアレイからさらに偶数だけを取り出したい。
偶数は2で割った余りが0な数なわけだから、Problem 1と同様の手段で

   g =. (4e6 > f) # f
   (0 = 2 | g) # g
2 8 34 144 610 2584 10946 46368 196418 832040 3524578

あとはこれらの総和を求めればいいわけだから

   +/ (0 = 2 | g) # g
4613732

纏めると

phi =. -: 1 + %: 5
fib =: 3 : '<. 0.5 + (phi ^ y + 2) % %: 5'
+/ (0 = 2 | g) # g =. (4e6 > f) # f =. fib i.40

Problem 3

13195 の素因数は 5、7、13、29 である。

600851475143 の素因数のうち最大のものを求めよ。

今度は素因数分解、しかも巨大な数を素因数分解する問題。
かなりややこしそうだし、かなり難しそうに感じるかもしれない。
だが、そこを何とかしてくれるのがJ言語である。

これはもう8割反則だが、Jには素因数分解を行ってくれる動詞がある。
その動詞は q: 。
q: は素因数をアレイにして返すので、後は最大値をとればいいだけ。
JにとってはProblem 3なんて朝飯前なわけだ。

   q:600851475143
71 839 1471 6857

最大値をとるには次の動詞をうまく使ってやる。

二項を比較して大きい方を返すわけだから、例えば「5 9 3 1 6」の最大値を求めるには

   5 >. 9 >. 3 >. 1 >. 6
9

という風に、全要素に対して <. をかけてやればいい。
ここで副詞 / の存在を思い出した奴は天才。
副詞 / を使って全要素に <. を挿入すれば最大値が得られる。

   >./ q:600851475143
6857

Problem 4

左右どちらから読んでも同じ値になる数を回文数という。 2桁の数の積で表される回文数のうち、最大のものは 9009 = 91 × 99 である。

では、3桁の数の積で表される回文数のうち最大のものはいくらになるか。

まあこれは所謂総当りで解けるだろう。
問題は、以下の二つになる。

というわけでまずは、3桁の数の積の表をどう作るかを考えよう。
まず、3桁の数をすべて用意する。

   100 + i.900
100 101 102 103 104 105 106 107 108 109 110 ...

で、これを使って積の表を作りたいわけだが、
表を作るには副詞 / を使う。

ここで注目すべきことは、副詞も単項と二項で動作が異なるという点。
副詞 / は単項の場合は「挿入」だったが、二項の場合は「表」になる。
副詞 / を二項で使った場合の例を見てみよう。

   2 3 4 */ 3 4 5 6   NB. 2 3 4と3 4 5 6の積の表
 6  8 10 12
 9 12 15 18
12 16 20 24

これを使って、3桁の数の積の表が作れる。

(100 + i.900) */ 100 + i.900

これでまあおkなんだが、このコードをよく見ると */ の二項の値が同じになっている。
これでは何だかスマートじゃないし、見てくれもあまりよくない。
そこで、 */ に更に副詞 ~ を追加する。
副詞 ~ は、単項を二項に拡張する。どういうことかと言うと、以下のような動作をする。
(ここで、 f は適当な動詞、 y は適当な名詞)

f~ y   →   y f y

つまり、 ~ を適用した動詞に引数を一個与えると、それが二個与えられたかのように動作する。 これは二項に同じ値がくるときに使うことができる。
よって、先ほどの3桁の数の積の表は以下のように短縮することができる。

*/~ 100 + i.900

次に、回文数の判定をしたいと思う。
回文数は逆に並べても同じになる数のことだから、実際に逆にしたものと元の数値を比較すればいい。
ここで、逆にするのを簡単にするために、数値を文字列化したいと思う。
値の文字列化には動詞 ": を使う。

kaibun =: ":    NB. とりあえず文字列化だけしてみる

次に、これを反転したものと比較したい。
文字列やアレイを反転するのには動詞 |. を使う。
で、次に比較だが、動詞 = は文字単位で比較するので使えない。
文字列やアレイ全体が全く同じかどうかを比較するには動詞 -: を使う。

   1 2 3 = 3 2 1
0 1 0
   1 2 3 -: 3 2 1
0
   'abcd' = 'abcd'
1 1 1 1 
   'abcd' -: 'abcd'
1

動詞 |. で反転したものと、元の文字列を比較するというコードは、
フックを利用して以下のように書ける。ぜひ解読にチャレンジしてほしい。

(-: |.)

で、これを動詞 kaibun に組み込みたいわけだが、
そのまま kaibun に入れてしまうと既にある ": とフックを起こしてしまう。
こちらの思惑としては ": で文字列化した結果を、 (-: |.)で比較したいのだが、
フックを起こされてしまうとこれができない。

そこで使うのが接続詞 @ である。
@ の動作は以下のように定義されている。 (f, gは適当な動詞、yは適当な名詞)

f @ g y   →   f g y

何も変わっていないように見えるが、これを使った例を見てほしい。

   f =: % @ -:
   g =: % -:
   f 1 2 3 4
2 1 0.666667 0.5
   g 1 2 3 4
2 2 2 2

@ を使用した動詞 f では、 % と -: がフックを起こさず
値を半分にして、その逆数をとっている。
一方使用しなかった動詞 g では、 % と -: がフックを起こし、
「値÷値の半分」つまり2を返す動詞になっている。

これを使えば、 ": と (-: |.) でフックを起こさないコードを書くことができる。

   kaibun =: (-: |.) @ ":
   kaibun 234
0
   kaibun 9009
1

ところが、この動詞はこのままだとアレイには適用できない。
なぜならアレイを渡すと、アレイを丸ごと文字列化して、アレイを丸ごと反転、比較してしまうので
アレイの各要素が回文数かどうかを判定してくれない。
そこで、接続詞 " を使う。

接続詞 " は「動詞 " 数値」という使い方をし、
動詞が影響するを、数値によって指定できる。
列といっても分かりにくいが、以下の例を見てほしい。

   t =. 3 4 $ i.12
   t
0 1  2  3
4 5  6  7
8 9 10 11
   +/ t    NB. 縦方向に和が求められる(+/ "1 tとしても動作は同じ)
12 15 18 21
   +/ "2 t   NB. 横方向に和が求められる
6 22 38
   +/ "0 t   NB. 各要素ごとに和が求められる(何もしない)
0 1  2  3
4 5  6  7
8 9 10 11

"0 と指定した例では、各要素ごとに +/ としているが、
当然意味がないので値はそのままである。

そこで動詞 kaibun に接続詞 " で0を指定してやれば、
アレイの各要素に kaibun が作用することになるから
アレイの各要素が回文数かそうでないかを判定することができる。

   kaibun 123 45654 2002 1010
0
   kaibun"0 (123 45654 2002 1010)   NB. 0 がアレイの一部と勘違いされないように括弧でくくる
0 1 1 0

で、これでいよいよ出来るか、と思ったがまだ問題がひとつある。
さっき作った3桁の数の積の表は二次元以上のものなので、kaibun"0 ではきちんと処理できない。
そこで、二次元以上の表を単純なアレイにするために動詞 , を使う。

   t =. 3 3 $ i.9
   t
0 1 2
3 4 5
6 7 8
   , t
0 1 2 3 4 5 6 7 8 9

さて、これでProblem 4を解くために必要な知識は全て得られた。
後は纏めのコードだけ置いておくので、残りの解読にチャレンジしてほしい。

kaibun =: (-: |.) @ ":
>./ (kaibun"0 t) # t =. , */~ 100 + i.900

ちなみにこの纏めのコードの空白を省いて、更にフック、フォークを駆使すると
Jの難解さがより際立つコードになる。

>./(((-:|.)@":)"0#]),*/~i.1000

こっちの解読もやってみるといいかも。難解なコードを読めると楽しいし。


トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2023-02-23 (木) 23:33:35