ラテン方陣(標準形)
前回、作ったコードで作成できるラテン方陣は、各行を巡回配置したパターンだけでした。実験計画法などで使うにはいいかも知れませんが、配置が単純なので数独のようなパズルには不十分です。
そこで、今回は標準形のラテン方陣を作成するコードを書いてみました。標準形とはラテン方陣の1行目と1列目が自然な順序(自然数なら1,2,3,...、アルファベットならA,B,C,...とか)で並んでいるモノを指します。すべてのラテン方陣は行または列を入替えることにより、標準形に変換することができます。逆に言えば、標準形が得られれば、行列を入替えることで全パターンのラテン方陣を生成することができるという訳です。
作成したコードは次のとおりです。一応、縦型探索で枝切りしてるつもりなんですけど、配列のコピーのコストでやたらと遅いような気がします。素直に、総当たりでチェックした方が早いかも。
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import argparse import copy class Square(object): def __init__(self, n): self.n = n self.rows = [] self.status = 'unknown' for i in range(n): row = [] for j in range(n): row.append(range(1,n+1)) self.rows.append(row) self.row = 0 self.col = 0 def get(self, row, col): return self.rows[row][col] def next(self): self.col = self.col + 1 if self.col >= self.n: self.col = 0 self.row = self.row + 1 if self.row >= self.n: self.row = -1 self.col = -1 def _set(self, row, col, value): self.rows[row][col] = value def _remove(self, row, col, value): v = self.get(row, col) if type(v) != list: return try: v.remove(value) except: pass if len(v) == 0: self.status = 'error' def set(self, row, col, value): v = self.get(row, col) for i in range(self.n): if i == col: continue self._remove(row, i, value) for i in range(self.n): if i == row: continue self._remove(i, col, value) self._set(row, col, value) def print_sq(self): for i in range(self.n): for j in range(self.n): print self.rows[i][j], print def clone(self): o = Square(0) o.n = self.n o.rows = copy.deepcopy(self.rows) o.status = self.status o.row = self.row o.col = self.col return o @staticmethod def initial(n): sq = Square(n) if n == 1: sq.set(0,0,1) else: for i in range(sq.n): sq.set(0,i,i+1) for i in range(sq.n): sq.set(i,0,i+1) sq.row = 1 sq.col = 1 return sq def ReducedLatinSquareGenerator(n): queue = [Square.initial(n)] while len(queue) > 0: e = queue.pop(0) if e.row == -1 and e.col == -1: if e.status == 'error': continue e.status = 'ok' yield e else: v = e.get(e.row, e.col) if type(v) == int: e.next() queue.insert(0,e) continue for i in range(len(v)): cp = e.clone() cp.set(cp.row, cp.col, v[i]) if cp.status == 'error': continue cp.next() queue.insert(i,cp) def main(): parser = argparse.ArgumentParser( description="""ラテン方格標準形作成""") parser.add_argument('-n', default=3, dest='size', type=int, help='方格の大きさ') args = parser.parse_args() if args.size <= 0: print "The size of latin-square must be more than zero." bar = "-"*args.size*2 n = 0 for e in ReducedLatinSquareGenerator(args.size): n = n + 1 print bar print "No:%d" % n print bar e.print_sq() print bar print u'The number of reduced latin squares of size %d = %d' % (args.size, n) if __name__ == "__main__": main()
実行してみた結果は、次のような感じになります。
$ ./reducedlatinsquare.py -n 4 -------- No:1 -------- 1 2 3 4 2 1 4 3 3 4 1 2 4 3 2 1 -------- No:2 -------- 1 2 3 4 2 1 4 3 3 4 2 1 4 3 1 2 -------- No:3 -------- 1 2 3 4 2 4 1 3 3 1 4 2 4 3 2 1 -------- No:4 -------- 1 2 3 4 2 3 4 1 3 4 1 2 4 1 2 3 -------- The number of reduced latin squares of size 4 = 4 $