Zebra PuzzleをPythonで解いてみた。(2)

 拙ブログの記事『Zebra PuzzleをPythonで解いてみた。』の続きです。前回のプログラムにマトリクス(グリッド)表を付けてみました。(^_^;
 大きなマトリクス表を描いてみたかったので作ってみましたが、ラベルが半角英数字だったので対策を講じることになりました。2番目の参考URLを参考にして、全角文字を2文字としてカウントするlenB()関数を作りました。これを使って、半角1文字のときは前に空白を補うことにしました。
 それから、Python2では、unicodedata.east_asian_width()関数を使うためには引数をユニコード文字列に変換しておく必要があるようです。
 ちなみに、今日で、「書いた日数」が500日になります。プラチナまであと半分まで来ました。まだまだ先は長いねぇ。(^_^;

● Zebra2.py

# coding: UTF-8
# Zebra2.py
'''
 H: 12345
 C: YBRWG
 N: NUESJ
 D: WTMOC
 S: KCOLP
 P: FHSDZ
'''
import itertools
from time import time
import unicodedata
import sys

def lenB(s):
    if sys.version_info[0]==2: s = unicode(s)   # Python3では不要
    w = 0
    for c in s:
        d = unicodedata.east_asian_width(c)
        w+=(1 if d in 'HNa' else 2)
    return w

# ラベルと答えのリスト等からマトリクス表を作成
def mkMatTbl(lbl,ans):
    m,n = len(lbl),len(lbl[0])
    li = [lbl[i] for i in range(1,m)]       # 横軸ラベルの作成
    s = '  '
    for i in range(m-1):
        for j in range(n):
            t = li[i][j]
            if not isinstance(t,int):
                if lenB(t)==1: s+=" "
                s+=t
            else: s+=('%2s'%str(t))[:2]
    print(s)
    for h in range(m-1):
        for i in range(n):
            s,t = '',lbl[-h][i]
            if not isinstance(t,int):       # 縦軸ラベルの作成
                if lenB(t)==1: s+=' '
                s+=t
            else: s = ('%2s'%str(t))[:2]
            g = ans[-h].index(t)
            for j in range(1,m-h):
                a = ans[j][g]
                for k in range(n):
                    s+=' o' if lbl[j][k]==a else ' x'
            print(s)

# 条件チェック: xからm個右側にyがあるか調べる
def check_cond(m, a, x, b, y):
    n = a.index(x)
    if n==ValueError or len(a)<=m+n:
        return False
    return b[m+n]==y

# マッチング条件
def check_match(a, x, b, y):
    return check_cond(0, a, x, b, y)

# 順序付き隣接条件(左からx,yの順で隣接)
def check_adj_1(a, x, b, y):
    return check_cond(1, a, x, b, y)

# 隣接条件(順不定)
def check_adj_2(a, x, b, y):
    return check_adj_1(a,x,b,y) or check_adj_1(b,y,a,x)

def main():
    tm = time() # Timer Start

    # 要素を昇順にソートしたもの
    HOUSE  = '12345'    # 家の場所(固定)
    COLOR  = 'BGRWY'    # 家の色
    NATION = 'EJNSU'    # 国、国籍
    DRINK  = 'CMOTW'    # 飲み物
    SMOKE  = 'CKLOP'    # タバコ
    PET    = 'DFHSZ'    # ペット

    # 連想配列
    Nation = {
        "E" : u"E:イギリス",
        "J" : u"J:日本",
        "N" : u"N:ノルウェー",
        "S" : u"S:スペイン",
        "U" : u"U:ウクライナ"
    }

    house = HOUSE[:]
    for nation in itertools.permutations(NATION):               # nation loop
        if not check_match(nation,'N',house, '1'): continue                 # 条件10
        for color in itertools.permutations(COLOR):             # house loop
            if not check_match(nation,'E',color, 'R'): continue             # 条件02
            if not check_adj_1(color, 'W',color, 'G'): continue             # 条件06
            if not check_adj_2(nation,'N',color, 'B'): continue             # 条件15
            for pet in itertools.permutations(PET):             # pet loop
                if not check_match(nation,'S',pet,   'D'): continue         # 条件03
                for drink in itertools.permutations(DRINK):     # drink loop
                    if not check_match(nation,'U',drink, 'T'): continue     # 条件05
                    if not check_match(color, 'G',drink, 'C'): continue     # 条件04
                    if not check_match(house, '3',drink, 'M'): continue     # 条件09
                    for smoke in itertools.permutations(SMOKE): # tabaco loop
                        if not check_match(smoke, 'O',pet,   'S'): continue # 条件07
                        if not check_match(color, 'Y',smoke, 'K'): continue # 条件08
                        if not check_adj_2(smoke, 'C',pet,   'F'): continue # 条件11
                        if not check_adj_2(pet,   'H',smoke, 'K'): continue # 条件12
                        if not check_match(smoke, 'L',drink, 'O'): continue # 条件13
                        if not check_match(nation,'J',smoke, 'P'): continue # 条件14
                        # チェックを潜り抜けたものだけを表示
                        print("H: %s"%"".join(house ))
                        print("C: %s"%"".join(color ))
                        print("N: %s"%"".join(nation))
                        print("D: %s"%"".join(drink ))
                        print("S: %s"%"".join(smoke ))
                        print("P: %s"%"".join(pet   ))
                        print(u"∴")
                        print(u"(1)%s人"%Nation[nation[drink.index('W')]])
                        print(u"(2)%s人"%Nation[nation[pet  .index('Z')]])
                        print('')
                        mkMatTbl((HOUSE,COLOR,NATION,DRINK,SMOKE,PET), \
                                 (HOUSE,color,nation,drink,smoke,pet))

    print("Runtime : %.3f [sec]"%(time()-tm))   # Timer Stop & Print

if __name__ == '__main__':
    main()

●実行結果

H: 12345
C: YBRWG
N: NUESJ
D: WTMOC
S: KCOLP
P: FHSDZ
∴
(1)N:ノルウェー人
(2)J:日本人

   B G R W Y E J N S U C M O T W C K L O P D F H S Z
 1 x x x x o x x o x x x x x x o x o x x x x o x x x
 2 o x x x x x x x x o x x x o x o x x x x x x o x x
 3 x x o x x o x x x x x o x x x x x x o x x x x o x
 4 x x x o x x x x o x x x o x x x x o x x o x x x x
 5 x o x x x x o x x x o x x x x x x x x o x x x x o
 D x x x o x x x x o x x x o x x x x o x x
 F x x x x o x x o x x x x x x o x o x x x
 H o x x x x x x x x o x x x o x o x x x x
 S x x o x x o x x x x x o x x x x x x o x
 Z x o x x x x o x x x o x x x x x x x x o
 C o x x x x x x x x o x x x o x
 K x x x x o x x o x x x x x x o
 L x x x o x x x x o x x x o x x
 O x x o x x o x x x x x o x x x
 P x o x x x x o x x x o x x x x
 C x o x x x x o x x x
 M x x o x x o x x x x
 O x x x o x x x x o x
 T o x x x x x x x x o
 W x x x x o x x o x x
 E x x o x x
 J x o x x x
 N x x x x o
 S x x x o x
 U o x x x x
Runtime : 0.187 [sec]

※参考URL
Zebra Puzzle - Wikipedia
Pythonで全角文字を含む文字列の幅を取得する - Narrow Escape
Pythonバージョンの取得方法 - Qiita
買い物の推理パズルをPythonで解いてみた。(2)
願い事の推理パズルをPythonで解いてみた。

P.S.
 はてなダイアリーから、はてなブログに移行したとき、実行結果の表示が乱れるようになったので、('◯','×')を(' o',' x')に変更しました。