今日の寿司
中身が気になる御歳頃。
はじめに
最近運気が悪く、悪夢も良くみるようになったので、これは邪気を払う必要がある。なので、そういうのに使えそうなものと言えば何か考えたら、魔方陣ではないか、ということを考えていた。
昔の夢は何だったか思い出すと、科学者か魔法使いになりたかった思い出があり、極まったプログラマというのはWizardと呼ばれる。さすがに、自分がそのレベルに達しているとは思えないが、どうせだし、魔法陣くらいは作れるようになりたいと思ったので、今回のエントリを書く。以下、『魔方陣の世界』という参考書(エントリの最後に記述する)を元に書く。
魔方陣とはなにか
とはいえ、魔方陣とは何かについて、最初に書いておかないといけない。魔方陣とは、1から始まる連続した異なる自然数を碁盤の目状に並べて、それぞれの列、それぞれの行、そして対角線の数の和が一緒であるものを指す。具体的には、下のような形となる。
---------------- |14|25|08|16|02| ---------------- |03|11|22|09|20| ---------------- |17|04|15|23|06| ---------------- |10|18|01|12|24| ---------------- |21|07|19|05|13| ----------------
このような、5x5(以下「5次の魔方陣」と呼ぶ)は、その特性上、各和が65となる。試しに適当に抜きだしてみると、8 + 22 + 15 + 1 + 19
は30 + 15 + 20
とでき、合計が65になることが確認できる。
一般的に、n次の魔方陣の定和(つまり、それぞれの行や列の総和)はという公式で導き出すことができる。5次の場合、5 * (25 + 1) / 2 = 130 / 2 = 65
となる。これは1 + 2 + 3 .. n
の総和を出す公式がであることから由来している。これがもし「7次の魔方陣」であるならば、総和は175ということになる。7桁の魔方陣であるならば、次のようになる。
---------------------- |27|33|10|49|18|36|02| ---------------------- |07|25|29|09|48|19|38| ---------------------- |37|06|26|31|14|46|15| ---------------------- |17|42|04|22|30|13|47| ---------------------- |43|16|41|05|24|35|11| ---------------------- |12|45|21|39|01|23|34| ---------------------- |32|08|44|20|40|03|28| ----------------------
6桁の魔方陣が1600京以上(!!)あると言われていることから、当然のことながら、7桁の魔方陣も、それに匹敵する数存在していることは間違いない。しかし、その作りかたは、考え方さえわかってしまえば、至って簡単である。
二つの補助魔方陣を作る
まず、7次の魔方陣を作るさいに、7進法の考え方をする。例えば、8であるならば10、9であるならば11、といったように、7で位が上がる数のことを指す。
この時に、2の桁と1の桁を分離する。そして、1から7までの数を利用して、列同士では数は被っても良いので、定和性を満す魔方陣を作る(この場合ならば、1から7なので、28となる)。具体的にプログラミングで出力すると、次のような補助魔方陣である。
[4, 7, 6, 1, 5, 3, 2] [2, 4, 7, 6, 1, 5, 3] [3, 2, 4, 7, 6, 1, 5] [5, 3, 2, 4, 7, 6, 1] [1, 5, 3, 2, 4, 7, 6] [6, 1, 5, 3, 2, 4, 7] [7, 6, 1, 5, 3, 2, 4]
このとき、生成を簡単にするために、あるトリックを使っているのだが、それはとりあえずあとまわしにしよう。そして、もう一つの補助魔方陣を用意する。
[4, 6, 2, 1, 7, 3, 5] [1, 7, 3, 5, 4, 6, 2] [5, 4, 6, 2, 1, 7, 3] [2, 1, 7, 3, 5, 4, 6] [3, 5, 4, 6, 2, 1, 7] [6, 2, 1, 7, 3, 5, 4] [7, 3, 5, 4, 6, 2, 1]
まず魔方陣の最初の特性として、「同じ数を二度使わないことである」が挙げられる。とすると、この時、この二つの補助魔方陣を組みあわせた場合、同じ組みあわせである場合、それは同じ数であるわけだから、魔方陣としては成立していない。なので、補助魔方陣のお互いの要件の第一条件として、組みあわせたときに、「同じ数の組みあわせは二度あらわれない」となる。
利用する補助魔方陣の性質
ここからは、ちょっとややこしい話も含まれるので、コードを見せながら解説する。
まず補助魔方陣の一つ目の特徴だが、まず左上からスタートする対角線上に(n + 1) / 2
(この場合のnは「n次の魔方陣」のn)を引いたあと、それ以外の数はシャッフルしても良い。7の場合は4なので、4が対角線上に配置されていれば良い。
class Mahou DIMENSION = 7 def self.simple_a seed = [*1..DIMENSION] delete_elem = (DIMENSION + 1) / 2 seed.delete(delete_elem) seed.shuffle! seed.insert(delete_elem - 1, delete_elem) a = Array.new(DIMENSION) { seed.clone } r = delete_elem 0.upto(DIMENSION - 1) do |x| r -= 1 a[x].rotate!(r) r = DIMENSION - 1 if r < 0 end return a end end
さて、もう一つの補助魔方陣を作る必要があるのだが、ちょっと困ったことがある。それは法則上、nが3の倍数の時は違う法則性を持つ。従って、今回はnが3以外の倍数であるときのことを考える。
考え方としては簡単で、1からnまでの数字をまず適当に並べる。そのあと、対角線と並べた数字とは逆に、縦に並べていく。解りにくいだろうので、1から5の間の数字で説明すると、次のようになる。
[1, 3, 5, 2, 4] [5, 2, 4, 1, 3] [4, 1, 3, 5, 2] [3, 5, 2, 4, 1] [2, 4, 1, 3, 5]
縦方向に着目してもらえればわかるように、1の次は5,5の次は4というように、対角線に並べられた数字とは逆の数字が配置されている。同様に、2列目も、2の次は1, 1の次は循環して5……というように並べられている。これをコードにすると、やや泥臭いが次のようになる。
def self.simple_b b = Array.new(DIMENSION) { Array.new(DIMENSION) } raw_seed = [*1..DIMENSION].shuffle 0.upto(DIMENSION - 1) { |i| b[i][i] = raw_seed[i] } seed = raw_seed.reverse * DIMENSION 0.upto(DIMENSION - 1) do |x| point = seed.index(raw_seed[x]) start = x 1.upto(DIMENSION) do |y| b[start][x] = seed[point] point += 1 start += 1 start = 0 if start >= b.size end end return b end
組みあわせる
組みあわせるのは非常に簡単だ。2次元配列なので、入れ子状にしてあげればよい
def self.format_print m l = "---" * DIMENSION + "-" puts l m.each do |n| n.each do |e| print "|" printf "%02d", e end puts "|" puts l end end def self.simple a = simple_a b = simple_b m = Array.new(DIMENSION) { Array.new (DIMENSION) } a.each_with_index do |i, j| i.each_with_index do |k, n| m[j][n] = (k - 1) * DIMENSION + b[j][n] end end format_print m end
a
は7進法の二桁目、 b
は一桁目をさしている。ただし、二桁目に関しては、0である場合もあることを考慮しなければならない。なので、実際の計算には一度1を引いてやる必要がある。また、上記では考慮していないが、魔方陣に必要なのは、「それぞれの列、行、対角線の総和が一緒であること」なので、それをチェックするメソッドもあったほうがいいだろう。
def self.column_check a, b 0.upto(DIMENSION - 1) do |i| sum = 0 0.upto(DIMENSION - 1) do |j| sum += a[j][i] end raise "縦の行: #{j + 1} が #{sum} なので定和性を満しません" if sum != b end 0.upto(DIMENSION - 1) do |i| sum = a[i].inject(:+) raise "横の行: #{i + 1} が #{sum} なので定和性を満しません" if sum != b end sum = 0 0.upto(DIMENSION - 1) do |i| sum += a[i][i] end raise "左上からの対角線が #{sum} なので定和性を満しません" if sum != b sum = 0 0.upto(DIMENSION - 1) do |i| sum += a[i][DIMENSION - 1 - i] end raise "右上からの対角線が #{sum} なので定和性を満しません" if sum != b end
まとめ
というわけで、古典的な「魔方陣を解く」というコードを書いてみたけれども、意外と難儀した。多分、手元でやれば一瞬で終わることがコードにうまく落としこめないと、自分が終わっているような気がして気が沈むので良くないと思う。やはりコードを書いていくことは大切だということがわかったりした。
また、魔方陣が気軽にできるようになったので、悪いことが起きたら、気軽に実行して魔除けしたいと思った。これで運気が上がればなーと思っている。
ちなみに実行可能なコードはこちらです。
参考文献
- 作者: 大森清美
- 出版社/メーカー: 日本評論社
- 発売日: 2013/08/03
- メディア: 単行本
- この商品を含むブログを見る