すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

pythonで迷路探索するよ<解答紹介編>

こんばんは,1000chです. 最近Check iOが面白いのでいろんな人に布教しています. non pythonユーザに布教andしばらく様子を見た感じ,for文とlistの使い方になれが必要なのかなぁと感じています.

で,おそらくその辺の友人ズは最初の島の経路探索プログラムで詰まるんじゃないかな?と思ったので, 今回から2回に分けてちょっとした解説をしていこうかと思います.

だっさんの形式を参考に,今回まず解答コードを載せ,次回で以降で詳解していこうと思います. ではでは,いきますね.

問題

今回のターゲット問題は,はじめの島"Home"の"open labyrinth"という問題です. 問題文はこんな感じ.

The labyrinth has no walls, but pits surround the path on each side. If a player falls into a pit, they lose. The labyrinth is presented as a matrix (a list of lists): 1 is a pit and 0 is part of the path. The labyrinth's size is 12 x 12 and the outer cells are also pits. Players start at cell (1,1). The exit is at cell (10,10). You need to find a route through the labyrinth. Players can move in only four directions--South (down [1,0]), North (up [-1,0]), East (right [0,1]), West (left [0, -1]). The route is described as a string consisting of different characters: "S"=South, "N"=North, "E"=East, and "W"=West.

ざっくり言うと,「listで迷路情報が与えられるので,それを攻略する経路を出力してね」って感じですね. 最短とは書いてないような気もしますが,まぁ最短経路を求めれば委員じゃないかなと思います.

解答

さっそく,自分の解答を紹介します.

#Your code here
#You can import some modules or create additional functions

import math

class MazeNode:
    '''
    @params
    x:pos_x
    y:pos_y
    state:0:route,1:wall
    cond:'none','open','close'
    hs:h*(pos):goalまでの直線距離
    gs:適宜update:fs-hs
    fs:gs+hs
    parent:親node
    '''
    def __init__(self,x,y,st,cond):
        self.x = x
        self.y = y
        self.state = st
        self.cond = cond
        self.parent = None
        self.fs = 0
        self.hs = 0
        self.gs = 0

    def calc_hs(self,gx,gy):
        dx = self.x - gx
        dy = self.y - gy
        self.hs = math.sqrt(dx ** 2 + dy ** 2)
    
    def update_gs(self):
        self.gs = self.fs - self.hs

    def set_gs(self,val):
        self.gs = val

    def set_cond(self,cond):
        if cond == "open" or cond == "close":
            self.cond = cond
        else:
            print("cond set error")
    
    # debug
    def show_pos(self):
        print("(%d:%d) %d %s" % (self.x,self.y,self.state,self.cond))

def get_min_fs_node(maze_nodes):
    # open_nodeのうちmin_fsのものを1つreturn
    res_node = None
    for row in maze_nodes:
        for node in row:
            if node.cond != "open":
                continue
            if not res_node:
                res_node = node
            elif node.fs < res_node.fs:
                res_noe = node
    return res_node

def check_next_node(target_node,maze_nodes):
    # get next nods
    tx = target_node.x
    ty = target_node.y
    next_nodes = [
            maze_nodes[ty-1][tx  ],
            maze_nodes[ty  ][tx+1],
            maze_nodes[ty+1][tx  ],
            maze_nodes[ty  ][tx-1]
            ]

    # process next nodes
    target_node.update_gs()
    for node in next_nodes:
        if node.state == 1:
            continue
        f_dash = target_node.gs + node.hs + 1
        if node.cond == "none":
            node.set_cond("open")
            node.fs = f_dash
            node.parent = target_node
        elif node.cond == "open" and f_dash < node.fs:
            node.fs = f_dash
            node.parent = target_node
        elif node.cond == "close" and f_dash < node.fs:
            node.set_cond("open")
            node.fs = f_dash
            node.parent = target_node

# debug
def print_route(maze,fin_node):
    # make map data
    map = []
    for y in range(len(maze)):
        row = []
        for x in range(len(maze[0])):
            row.append("-" if maze[y][x] == 1 else " ")
        map.append(row)

    # make route data
    node = fin_node
    while node:
        map[node.y][node.x] = "x"
        node = node.parent

    # print route data
    for y in range(len(map)):
        for x in range(len(map[0])):
            print(map[y][x], end="")
        print("")

def make_route(fin_node):
    route = ""
    node = fin_node

    while node.parent:
        dx = node.x - node.parent.x
        dy = node.y - node.parent.y

        if dx == 1:
            route += "E"
        elif dx == -1:
            route += "W"
        elif dy == 1:
            route += "S"
        elif dy == -1:
            route += "N"
        
        node = node.parent

    return route[::-1]



def checkio(data):
    #Your code here
    #It's main function. Don't remove this function
    #It's using for auto-testing and must return a result for check.
    maze = data
    maze_nodes = []

    goal_pos_x = 10
    goal_pos_y = 10

    for y in range(len(maze)):
        row = []
        for x in range(len(maze[0])):
            node = MazeNode(x,y,maze[y][x],"none")
            node.calc_hs(goal_pos_x,goal_pos_y)
            row.append(node)
        maze_nodes.append(row)

    start_node = maze_nodes[1][1]
    start_node.set_cond("open")
    start_node.set_gs(0)
    start_node.fs = start_node.hs

    while True:
        target_node = get_min_fs_node(maze_nodes)
        print("target") 
        target_node.show_pos()
        if not target_node:
            print("search fail")
            break
        elif target_node.x == goal_pos_x and target_node.y == goal_pos_y:
            print("search finish")
            print_route(maze,target_node)
            return make_route(target_node)
            break
        else:
            target_node.set_cond("close")
            check_next_node(target_node,maze_nodes)




    #replace this for solution
    #This is just example for first maze
    return "SSSSSEENNNEEEEEEESSWWWWSSSEEEESS"

#Some hints
#Look to graph search algorithms
#Don't confuse these with tree search algorithms


#This code using only for self-checking and not necessary for auto-testing
if __name__ == '__main__':
    print(checkio([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
        [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1],
        [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1],
        [1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1],
        [1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]))
    print(checkio([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ]))
    #be careful with infinity loop
    print(checkio([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ]))
    print(checkio([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    ]))
    print(checkio([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],
        [1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1],
        [1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1],
        [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
        [1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ]))

check io に書いたモノのコピーですいませんw

経路探索といえば,昔授業でA*アルゴリズムとか紹介されたなぁと思い,wikipediaさんに詳細を聞きながら実装しました. だいたい3H弱で一気書きしたので,ツッコミどころは多々あるかもです. とはいえ定番問題なので,アルゴリズムちゃんと勉強した人だったらもっと早く書けるのかなとも思います. 精進せなあかんですね.

さて,では今回はこの辺で. 次回以降,結局このプログラム何してんの? ってとこをちょいちょい解説していきますね.