銀月の符号

Python 使い見習いの日記・雑記

迷路問題(人材獲得作戦・4)を解いてみる

人材獲得作戦・4 試験問題ほか の問題を解いてみた。今日は 13 日、もうすでにいろんな人が解き終わっていて、反応鈍かった感は否めない。

所要時間 40 分だった。最短経路に $ を書き込まなければならないので、ある地点へ「到達できたこと」に加えて「到達する前にいた地点」も保持するようにした。

改良するならば、データ保持の方法からかな。通過点を保持する visited は辞書(ハッシュ)である必要はなくリストで十分。

迷路の保持方法もリストのリストでなく、単なるリストで十分。メモリをさらにケチるなら、迷路の要素が通路、壁だけと限られていることを利用して一要素 1 byte の配列で表現、つまり array.array('b') などにする。さらにケチって 1 ビットに詰め込むのも不可能ではないが、 Python つかっておいてそれはやりすぎか。

# coding: utf-8

from collections import deque

def solve_maze(maze):
    u"""迷路の最短経路を求める

    迷路はテキストで表されているものとする。それぞれの意味は
        ' ' 通路
        '*' 壁
        'S' スタート
        'G' ゴール
    である。

    たとえば、次のようなものである。

    **************************
    *S* *                    *
    * * *  *  *************  *
    * *   *    ************  *
    *    *                   *
    ************** ***********
    *                        *
    ** ***********************
    *      *              G  *
    *  *      *********** *  *
    *    *        ******* *  *
    *       *                *
    **************************

    答えもテキストで表される。通過点は $ で示す。
    
    **************************
    *S* *$$$$                *
    *$* *$ *$ *************  *
    *$*$$$* $  ************  *
    *$$$ *  $$$$$$$          *
    **************$***********
    * $$$$$$$$$$$$$          *
    **$***********************
    * $$$  *$$$$$$$$$$$$$$G  *
    *  *$$$$$ *********** *  *
    *    *        ******* *  *
    *       *                *
    **************************

    ゴールにたどり着けない場合、 None を返す。

    迷路の外周が長方形となっていない、
    スタートやゴールがない、複数ある、別の文字が含まれているなどの
    入力は想定していなく、正しく動作しない。
    """

    course = []
    for i, line in enumerate(maze.splitlines()):
        line = line.rstrip('\n')
        s = line.find('S')
        if s >= 0:
            start = (s, i)
        g = line.find('G')
        if g >= 0:
            goal = (g, i)
        course.append(list(line))

    cols = len(course[0])
    rows = len(course)

    visited = {start: None}
    queue = deque([start])
    i = 0
    while queue:
        if goal in visited:
            break
        for _ in xrange(len(queue)):
            pos = queue.popleft()
            for p in ((0, 1), (0, -1), (-1, 0), (1, 0)):
                new_pos = (pos[0] + p[0], pos[1] + p[1])
                if not (0 <= new_pos[0] < cols):
                    continue
                if not (0 <= new_pos[1] < rows):
                    continue
                if course[new_pos[1]][new_pos[0]] != '*':
                    if new_pos not in visited:
                        visited[new_pos] = pos
                        queue.append(new_pos)
                        if new_pos == goal:
                            break
            if goal in visited:
                break
        i += 1
    else:
        # ゴールにたどり着けなかった
        return None

    pos = goal
    while True:
        pos = visited[pos]
        if pos == start:
            break
        course[pos[1]][pos[0]] = '$'

    answer = []
    for line in course:
        answer.append(''.join(line))
        answer.append('\n')

    return ''.join(answer)

def main():
    import sys
    input = sys.stdin.read()
    output = solve_maze(input)
    if output:
        sys.stdout.write(output)
    else:
        sys.stdout.write('no answer.')

if __name__ == '__main__':
    main()

追記。 Windows 上の Python (自分もなのだけれども)でこれを動かす時には spam.py < input.txt のようなコマンドでは動かない。 python spam.py < input.txt とする必要がある。標準入力の使用に一癖あるようだ。