休日の調べもの

調べものをしたときのメモ

イラストロジックをだいたい解けるプログラム

#
import array

class ImageBoard:
    board=
    x_size=0
    y_size=0
    def __init__(self,xs,ys):
        self.x_size=xs
        self.y_size=ys
        for i in range(self.y_size):
            line=[0]*self.x_size
            self.board.append(line)
    def dump(self):
        for y in range(self.y_size):
            for x in range(self.x_size):
                if(self.board[y][x]==0):
                    print("?",end="")
                elif(self.board[y][x]==1): #fill
                    print("#",end="")
                else:
                    print("X",end=""#space
            print("")
    def get_rows(self,y):
        return self.board[y]
    def set_rows(self,y,row):
        for i in range(x_size):
            if(self.board[y][i]==0):
                self.board[y][i]=row[i]
    def get_cols(self,x):
        ret=[0]*x_size
        for i in range(y_size):
            ret[i]=self.board[i][x]
        return ret
    def set_cols(self,x,col):
        for i in range(y_size):
            if(self.board[i][x]==0):
                self.board[i][x]=col[i]

#
# 与えられた空白の数を隙間のに割り当てる
# 隙間の数の組み合わせを作る
# 1が黒、2が白
# 例)
#  □■■□□■■□■■□
#  2,1,1,2,2,1,1,2,1,1,2
#
# yield を使うともっと簡潔に書けるかも
#
# num 割り振る値の合計値
# cell_cnt 割り振る場所の数

def combination_generator(num,cell_cnt):
    ans=
    max_num=num-cell_cnt
    if(cell_cnt==1):
        ans.append([num])
    elif(cell_cnt==2):
        for i in range(max_num+1):
            ans.append([max_num+1-i,i+1])
    else:
        for i in range(1,max_num+2):
            ans_list=combination_generator(num-i,cell_cnt-1)
            for l in ans_list:
                ans.append([i]+l)
    return ans

#
# 列が指定の条件を満たしているか
# 0=未定 なら何でもよい
# その他は 1,2が一致している
#
#
def check_rows(target_cells,test_cells):
    flag=True
    for i in range(len(target_cells)):
        if(target_cells[i]>0 and target_cells[i]!=test_cells[i]):
            flag=False
    return flag
#
#
# 両サイドのエッジは0 ~ 最大自由度
# セルの中身は両サイドの自由度を引いた後が最大、最低が隙間の数。
#
def make_fill_pattern(target_cells,clue):
    match_patterns=
    cell_cnt=len(target_cells) #長さ
    num_cnt =len(clue) #数字の数
    num_sum =sum(clue) #リストの数字の合計
    if(cell_cnt==num_sum):
        match_patterns.append([1]*cell_cnt)
    elif(num_sum==0):
        match_patterns.append([2]*cell_cnt)
    else:
        space_cnt=cell_cnt-(num_sum+num_cnt-1#調整可能な空白
        vacant_cell_cnt=num_cnt-1 #隙間の数
        #print(cell_cnt,num_cnt,num_sum,space_cnt,vacant_cell_cnt)    
        if vacant_cell_cnt==0:#途中に空白がない場合
            for lc in range(space_cnt+1):                
                rc=space_cnt-lc
                candidate=[2]*lc+[1]*num_sum+[2]*rc
                if check_rows(target_cells,candidate):
                    match_patterns.append(candidate)
        else:
            for lc in range(space_cnt+1):  #左右の隙間を確保
                for rc in range(space_cnt-lc+1):
                    patterns=
                    usable_space_cnt=cell_cnt-lc-rc-num_sum
                    space_patterns=combination_generator(usable_space_cnt,vacant_cell_cnt)
                    for p in space_patterns:
                        p.extend([0]) #ループの最後対策
                        candidate=[]
                        for m in clue:
                            candidate.extend([1]*m)
                            candidate.extend([2]*p.pop(0))
                        candidate=[2]*lc+candidate+[2]*rc #比較候補
                        if check_rows(target_cells,candidate):
                            match_patterns.append(candidate)
    return match_patterns
#
# 1次元配列のtarget_cellsに対して、clue(ヒント)を満たす候補をすべて求めて比較
#候補が1つしかなければ決定
#全てセルで同じ結果になるのであれば、そのセルは決定
#縦のセルは1次元で横にして渡す
#
def think_by_line(target_cells,clue):
    cell_cnt=len(target_cells)
    if target_cells.count(0)==0:
        return [0]*cell_cnt #この列は決定している
    patterns=make_fill_pattern(target_cells,clue)
    if len(patterns)==1:
        fill_ptn=patterns[0]
    else:
        fill_ptn=[0]*cell_cnt
        for p in patterns:
            for i in range(cell_cnt):
                if(fill_ptn[i]==0):
                    fill_ptn[i]=p[i]
                elif(fill_ptn[i]!=p[i]):
                    fill_ptn[i]=3
        for i in range(cell_cnt):
            if( (target_cells[i]>0 and target_cells[i]!=fill_ptn[i]) or fill_ptn[i]==3):
                fill_ptn[i]=0
    return fill_ptn                
#
# パズルを解く本体 上から下まで横一列を解いた後、左から右に縦1列を解く
# 一通りやって、変更したセルがなくなったら終了
# 30x30でも5回ぐらいで解ける
#
def solve_main(x_size,y_size,max_challenge):
    for i in range(max_challenge):
        chg=0
        print"%d times try "%(i+1))
        for y in range(y_size):
            line=ib.get_rows(y)
            ret=think_by_line(line,row_clue[y])
            ib.set_rows(y,ret)
            chg += len(ret)-ret.count(0)
        for x in range(0,x_size):
            line=ib.get_cols(x)
            ret=think_by_line(line,col_clue[x])
            ib.set_cols(x,ret)
            chg += len(ret)-ret.count(0)
        if(chg==0):
            return True
    return False
            
if __name__ == "__main__":
    #問題をここに書く
    col_clue=[[2],[4],[6],[8],[8],[6],[4],[2]]
    row_clue=[[2],[4],[6],[8],[8],[6],[4],[2]]
    max_challenge=8
    x_size=len(col_clue)
    y_size=len(row_clue)
    print(x_size,y_size)
    ib=ImageBoard(x_size,y_size)
    if(solve_main(x_size,y_size,max_challenge)):
        print("Solved")
    else:
        print("Give Up!")
    ib.dump()