Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

運気を上げるために魔方陣を作る(初級編)

今日の寿司

f:id:nisemono_san:20160808145908j:plain

中身が気になる御歳頃。

はじめに

最近運気が悪く、悪夢も良くみるようになったので、これは邪気を払う必要がある。なので、そういうのに使えそうなものと言えば何か考えたら、魔方陣ではないか、ということを考えていた。

昔の夢は何だったか思い出すと、科学者か魔法使いになりたかった思い出があり、極まったプログラマというのは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 + 1930 + 15 + 20とでき、合計が65になることが確認できる。

一般的に、n次の魔方陣の定和(つまり、それぞれの行や列の総和)は\frac{n(n^{2} + 1)}{2}という公式で導き出すことができる。5次の場合、5 * (25 + 1) / 2 = 130 / 2 = 65となる。これは1 + 2 + 3 .. nの総和を出す公式が\frac{n(n+1)}{2}であることから由来している。これがもし「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

まとめ

というわけで、古典的な「魔方陣を解く」というコードを書いてみたけれども、意外と難儀した。多分、手元でやれば一瞬で終わることがコードにうまく落としこめないと、自分が終わっているような気がして気が沈むので良くないと思う。やはりコードを書いていくことは大切だということがわかったりした。

また、魔方陣が気軽にできるようになったので、悪いことが起きたら、気軽に実行して魔除けしたいと思った。これで運気が上がればなーと思っている。

ちなみに実行可能なコードはこちらです

参考文献

魔方陣の世界

魔方陣の世界