最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)

← 前のページに戻る

魔方陣

魔方陣について、デカルト言語での例題として説明します。

魔方陣とは、たて、よこ、ななめのどの方向で足しても同じ数になる方形の数字の並びです。

ここで示すのは、奇数の3, 5, 7の長さの魔方陣を出力するプログラムです。

魔方陣は、破邪の護符として使われるとも聞きます。 このプログラムの出力を持っていると、魔よけとなるかもしれませんよ。

実行結果を示します。


$ descartes mahojin

  4   9   2
  3   5   7
  8   1   6

 11  18  25   2   9
 10  12  19  21   3
  4   6  13  20  22
 23   5   7  14  16
 17  24   1   8  15

 22  31  40  49   2  11  20
 21  23  32  41  43   3  12
 13  15  24  33  42  44   4
  5  14  16  25  34  36  45
 46   6   8  17  26  35  37
 38  47   7   9  18  27  29
 30  39  48   1  10  19  28

たて、よこ、ななめの和がすべて等しくなっているか確かめてみてください。

1. ソース

ソース: mahojin


<pprint #n>
        <for (#i #n)
                <for (#j #n)
                        <printf <%_ "3d" <p _ (#j #i)>> " ">
                >
                <print>
        >
        <print>
        ;

<mahojin #n>
        <#nn = #n*#n>
        <erase p>
        <setVar vx <_ = #n/2>>
        <setVar vy <_ = #n-1>>

        <for (#k 1 #nn)
                <setArray p #k (<vx #x> <vy #y>)>

                <#x1 = (#x + 1) % #n>
                <#y1 = (#y + 1) % #n>

                ( <p _ (#x1 #y1)>
                  <setVar vy <_ = (#y - 1 + #n) % #n>>
                 |
                  <setVar vx <_ = (#x + 1 + #n) % #n>>
                  <setVar vy <_ = (#y + 1 + #n) % #n>>
                )
        >

        <pprint #n>
        ;


<mj>
        <for (#i 1 3)
                <mahojin <_ = #i*2+1>>
        >
        ;

? <mj>;


最初にpprint述語を見てみます。 この述語は、魔方陣の結果を出力するものです。引数の#nは、魔方陣の1辺の長さを設定します。


<pprint #n>
        <for (#i #n)
                <for (#j #n)
                        <printf <%_ "3d" <p _ (#j #i)>> " ">
                >
                <print>
        >
        <print>
        ;

最初のfor述語<for (#i #n) ~>は、#iに0から#n-1の値を設定しながらループさせるものです。 次のfor述語 <for (#j #n) ~>は、さらにその中で#jに0から#n-1の値を設定しながらループさせるものです。 この2つのfor述語により、2重のループを構成します。

続く、printf述語は、配列pの値をプリントアウトするものです。

デカルト言語では、配列も述語の形式になります。

つまり、<p _ (#j #i)> は、2次元配列pの(#i, #j)の値を示します。 配列pの値は、次に示すことになるmahojin述語の中で設定されるものです。

まず、pの第一引数の_は無名変数です。 デカルト言語では、述語の第1引数の値をその述語の関数値とみなします。 上記では、p配列の値が関数値として、printf述語に渡されて、プリントアウトされます。 %述語は出力する値をフォーマットします。 ここでは、"3d"を指定しているので、整数で3桁をプリントアウトすることになります。 ここで指定できるフォーマットは、C言語のprintf関数のフォーマットと同じです。

たったこれだけの述語定義なのですが、結構デカルト言語固有の特徴的な機能を使っていますね。

その他、引数のないprint述語を使っていますが、これは改行を出力するために使っています。 魔方陣の横1行を表示したところで改行します。 また、全体を出力した後にも、1行改行して、次の魔方陣の出力と区別できるようにしています

次にあるmahojin述語は、魔方陣を計算する部分です。

奇数の魔方陣を作成するアルゴリズムとして以下を使います。


1) 下端のちょうど真ん中に1を入れます。
2) 数字は右下に順に入れていくのですが、1の下には欄がありません。
   このように欄をはみ出した場合には、反対側の上端の欄に入れます。
3) 数字を順に右下に入れていき、下端からはみ出した場合には、
   反対側の上端に入れます。
4) すでに数が埋まっている欄に当たった場合には、すぐ上の欄にいれます。
5) 上記を繰り返し、すべての欄が埋められたら終了します。
	

よく知られた方法です。 まだまだ、他にもいろいろな作成方法が魔方陣にはあります。 ぜひ、調べてプログラムしてみてください。


<mahojin #n>
        <#nn = #n*#n>
        <erase p>
        <setVar vx <_ = #n/2>>
        <setVar vy <_ = #n-1>>

        <for (#k 1 #nn)
                <setArray p #k (<vx #x> <vy #y>)>

                <#x1 = (#x + 1) % #n>
                <#y1 = (#y + 1) % #n>

                ( <p _ (#x1 #y1)>
                  <setVar vy <_ = (#y - 1 + #n) % #n>>
                 |
                  <setVar vx <_ = (#x + 1 + #n) % #n>>
                  <setVar vy <_ = (#y + 1 + #n) % #n>>
                )
        >

        <pprint #n>
        ;

では、mahojin述語の定義を順に細かく見ていきます。

<#nn = #n*#n>は、整数の演算をします。本当は、この部分は、let述語の簡略形であり、本来は<let #nn = #n*#n>とします。デカルト言語では、整数の演算については、letを省略することができます。

次の<erase p>は、システムに組み込まれた述語であり、配列pを削除します。 これは、繰り返しmahojin述語が呼ばれた場合に備えて配列pを初期化するためです。 なお、eraseは、配列だけでなく、変数、オブジェクト、述語定義の削除も行えます。 削除する対象が存在しなくても、erase述語の実行は無条件に成功します。 そのため、eraseを呼ぶ前に削除対象が存在するか判定しなくても大丈夫です。

setVarも組み込み述語です。 第1引数のグローバル変数に、第2引数の値を設定します。 上記では、第2引数に関数述語を設定しています。 関数述語は、述語の実行結果の第1引数の値を関数の返り値のごとく扱うのです。

よって、<_ = #n/2>は、<let _ = #n/2>の簡略形であり、関数述語として#n/2の値を計算して返します。

次のfor述語の中の、setArrayはグローバル配列に値を設定します。


   <setArray p       #k      (<vx #x> <vy #y>)>
             配列名  設定値        インデックス

設定された配列は(変数も同じなのですが)、<配列名 設定値 (インデックス)>のような述語として追加されます。


  )、<p 設定値 (インデックス)>

グローバル配列pは、値を読み出すときには、述語pとしてアクセスすることになります。

そのため、デカルト言語では、グローバル配列とグローバル変数と述語の名前は同じものを使わないようにするのがコツです。

同じ名前を使ってしまうと思わぬ誤動作やバグの原因となります。