使用Python查找所有迷宫解决方案 [英] Finding all maze solutions with Python

查看:70
本文介绍了使用Python查找所有迷宫解决方案的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试(使用Python)找到迷宫的所有可能解决方案.我有一个DFS脚本,它返回一个解决方案.我正在尝试适应它,但是我真的很难把头放在整个递归过程上.

I am trying to find (using Python) all possible solutions to a maze. I have a DFS script that returns one solution. I am trying to adapt it but I'm really having a hard time wrapping my head around the whole recursion thing.

这是我拥有的代码,可用于使用DFS查找一种可能的解决方案:任何提示或帮助将不胜感激!(数组中的字母"可以忽略/认为是常规的路径")

Here's the code I have, which works for finding one possible solution using DFS: Any tips or help would be much appreciated! (The "lett" in the array can be ignored/considered regular "path")

def DFS(x,y,Map):
    if (Map[x][y]=="exit"):                             #check if we're at the exit
        return [(x,y)]                                  #if so then we solved it so return this spot
    if ((Map[x][y]!="path") and (Map[x][y]!="lett")):   #if it's not a path, we can't try this spot
        return []
    Map[x][y]="explored"                                #make this spot explored so we don't try again
    for i in [[x-1,y],[x+1,y],[x,y-1],[x,y+1]]:         #new spots to try
        result = DFS(i[0],i[1],Map)                     #recursively call itself
        if len(result)>0:                               #if the result had at least one element, it found a correct path, otherwise it failed
            result.append((x,y))                        #if it found a correct path then return the path plus this spot
            return result
    return []                                           #return the empty list since we couldn't find any paths from here

def GetMap():
    return [
        ["wall","wall","wall","wall","wall","wall","wall","wall"],
        ["wall","path","path","path","path","path","path","wall"],
        ["wall","wall","wall","path","wall","lett","path","wall"],
        ["wall","path","path","path","wall","wall","path","wall"],
        ["wall","path","wall","lett","path","path","path","wall"],
        ["wall","path","wall","wall","wall","wall","path","wall"],
        ["wall","path","lett","path","path","path","exit","wall"],
        ["wall","wall","wall","wall","wall","wall","wall","wall"]
    ]

def DrawMap(Map,path):
    for x in range(0,len(Map)):
        for y in range(0,len(Map[x])):
            if ((x,y) in path):
                assert Map[x][y] in ("path","lett","exit")
                print("-",end="")
            elif (Map[x][y]=="wall"):
                print("#",end="")
            elif (Map[x][y]=="exit"):
                print("e",end="")
            elif (Map[x][y]=="lett"):
                print("L",end="")
            else:
                print(' ',end="")
        print()

print("\nUnsolved:\n")
DrawMap(GetMap(),[])
print("\n")

print("Solved with DFS:")
print("path is ",len(DFS(1,1,GetMap()))," spots long\n")
DrawMap(GetMap(),DFS(1,1,GetMap()))
print("\n")

推荐答案

如意算盘

像这样的问题的发作可能让人感到不知所措,但是我最喜欢的编程技术使复杂性消失van尽.使用一厢情愿,我们按照自己的意愿编写程序,然后实现我们的愿望.-

The onset of a problem like this can feel overwhelming, but my favourite technique in programming makes complexity vanish into thin air. Using wishful thinking, we write our program how we wish it could be, then we make our wishes come true -

# simple.py

from maze import maze
from cursor import cursor

def dfs(cursor, maze):
  q = maze.get(cursor.y(), cursor.x())
  if not q or q.is_wall() or q.is_step():
    return
  elif q.is_exit():
    yield maze
  else:
    next_maze = maze.step(cursor.y(), cursor.x())
    yield from dfs(cursor.up(), next_maze)
    yield from dfs(cursor.down(), next_maze)
    yield from dfs(cursor.right(), next_maze)
    yield from dfs(cursor.left(), next_maze)

def solve(cursor, maze):
  for x in dfs(cursor, maze):
    return x

我们所要做的只是一个迷宫 m 和一个光标 c -

All we need to get going is a maze, m, and a cursor, c -

# simple.py (continued)

# read maze from file
m = maze.from_file("./input")

# initialize cursor
c = cursor.from_ints(1, 1)

我们可以使用 solve -

# simple.py (continued)

print(solve(c, m))

########
#---   #
###-#L #
#  -## #
# #----#
# ####-#
# L   e#
########

或者我们可以使用 dfs -

# simple.py (continued)

for x in dfs(c, m):
  print(x, end="\n\n")

(下面的输出重新格式化以节省空间)

(output below reformatted to save space)

########   ########   ########   ########   ########   ########
#---   #   #---   #   #----- #   #----- #   #------#   #------#
###-#L #   ###-#L #   ### #--#   ### #--#   ### #L-#   ### #L-#
#  -## #   #---## #   #   ##-#   #---##-#   #   ##-#   #---##-#
# #----#   #-#L   #   # #L  -#   #-#----#   # #L  -#   #-#----#
# ####-#   #-#### #   # ####-#   #-#### #   # ####-#   #-#### #
# L   e#   #-----e#   # L   e#   #-----e#   # L   e#   #-----e#
########   ########   ########   ########   ########   ########


光标

要使上述程序正常运行,我们需要实现我们的所有愿望.我们将从 cursor 模块开始.光标只是一对整数,它使我们可以方便地进行 up down left right 移动-

To make the program above work, we need to fulfill all of our wishes. We'll start with the cursor module. A cursor is simply a pair of integers that gives us a convenient up, down, left, and right movements -

# cursor.py

def from_ints(y, x):
  return (y, x)

def up(t):
  (y, x) = t
  return from_ints(y - 1, x)

def down(t):
  (y, x) = t
  return from_ints(y + 1, x)

def left(t):
  (y, x) = t
  return from_ints(y, x - 1)

def right(t):
  (y, x) = t
  return from_ints(y, x + 1)

def to_str(t):
  (y, x) = t
  return f"({y}, {x})"

如您所见,我们正在使用普通功能.Python也具有出色的面向对象功能,我们希望将此类便利扩展到我们模块的用户.我们可以通过包装普通函数轻松添加OOP接口-

As you can see, we are working with ordinary functions. Python has nice object-oriented features too and we wish to extend such conveniences to the users of our module. We easily add an OOP interface by wrapping the plain functions -

# cursor.py (continued)

class cursor:
  def from_ints(y, x): return cursor(from_ints(y, x))
  def __init__(self, t): self.t = t
  def __iter__(self): yield from self.t
  def __str__(self): return to_str(self.t)
  def up(self): return cursor(up(self.t))
  def down(self): return cursor(down(self.t))
  def right(self): return cursor(right(self.t))
  def left(self): return cursor(left(self.t))


迷宫

现在,我们进入迷宫迷宫模块.我们将首先编写普通函数以将 from_file 转换为迷宫,并从迷宫 to_str -

Now we move onto our maze module. We'll start by writing ordinary functions to convert from_file to a maze, and from a maze to_str -

# maze.py

from cell import cell

def from_file(filename):
  with open(filename) as f:
    return from_str(f.read())

def from_str(s):
  return [ list(map(cell.from_str, row)) for row in s.split("\n") ]

def to_str(t):
  return "\n".join("".join(map(str, row)) for row in t)

作为奖励,请注意我们如何免费获得 from_str .接下来,我们使用 y x 坐标将函数编写为 get set 单元格.在这里,我们还编写了 step ,它是围绕 set 的简单包装器,用于标记迷宫中的某个单元已被探索.-

As a bonus, notice how we got from_str for free. Next we write functions to get or set a cell using y and x coordinates. Here we also write step, a simple wrapper around set, which is used to mark that a cell in the maze has been explored -

# maze.py (continued)

from arr_ext import update

def get(t, y, x):
  try:
    if x < 0 or y < 0:
      return None
    else:
      return t[y][x]
  except IndexError:
    return None

def set(t, y, x, v):
  return update \
    ( t
    , y
    , lambda row: update(row, x, lambda _: v)
    )

def step(t, y, x):
  return set(t, y, x, cell.step())

不要害怕随心所欲.我们将在需要时实施 update .就像我们在上一个模块中所做的一样,我们添加了面向对象的接口-

Don't be afraid to make as many wishes as you want. We'll implement update when we need it. And just like we did in the previous module, we add the object-oriented interface -

# maze.py (continued)

class maze:
  def from_file(filename): return maze(from_file(filename))
  def from_str(s): return maze(from_str(s))
  def __init__(self, t): self.t = t
  def __iter__(self): yield from self.t
  def __str__(self): return to_str(self.t)
  def get(self, y, x): return get(self.t, y, x)
  def set(self, y, x, v): return maze(set(self.t, y, x, v))
  def step(self, y, x): return maze(step(self.t, y, x))


单元格

当我们编写Maze模块时,我们希望有一个 cell 模块.如意算盘的技巧现在应该成为焦点:许下愿望,实现它.我们的细胞"模块代表迷宫中的一个细胞.我们从将 from_str 转换为单元格以及从单元格 to_str -

When we wrote the Maze module, we wished for a cell module. The technique of wishful thinking should be coming into focus now: make a wish, fulfill it. Our Cell module represents a cell in our maze. We start with a way to convert from_str to a cell, and from a cell to_str -

# cell.py

wall = 0
path = 1
exit = 2
lett = 3
step = 4

str_to_cell = \
  { "#": wall, " ": path, "e": exit, "L": lett, "-": step }

cell_to_str = \
  { v: k for (k, v) in str_to_cell.items() }

def from_str(s):
  if s in str_to_cell:
    return str_to_cell[s]
  else:
    raise RuntimeError(f"invalid cell character: {s}")

def to_str(t):
  if t in cell_to_str:
    return cell_to_str[t]
  else:
    raise RuntimeError(f"invalid cell component: {t}")

此外,我们编写 is _ * 谓词来确定单元格是否是 wall path 等.这突出了抽象的优势:我们可以更改数据在一个模块中的表示方式,而无需修改程序中的其他模块.-

Additionally we write is_* predicates to determine whether a cell is a wall, a path, etc. This highlights a strength of abstraction: we can change how our data is represented in one module without having to modify other modules in our program -

# cell.py (continued)

def is_wall(t): return t == wall
def is_path(t): return t == path
def is_exit(t): return t == exit
def is_lett(t): return t == lett
def is_step(t): return t == step

添加面向对象的界面.再次,这是我们普通函数的简单包装器-

Add the object-oriented interface. Again, it's a simple wrapper around our plain functions -

# cell.py (continued)

class cell:
  def from_str(s): return cell(from_str(s))
  def wall(): return cell(wall)
  def path(): return cell(path)
  def exit(): return cell(exit)
  def lett(): return cell(lett)
  def step(): return cell(step)
  def __init__(self, t): self.t = t
  def __str__(self): return to_str(self.t)
  def is_wall(self): return is_wall(self.t)
  def is_path(self): return is_path(self.t)
  def is_exit(self): return is_exit(self.t)
  def is_lett(self): return is_lett(self.t)
  def is_step(self): return is_step(self.t)


arr_ext

只有一个愿望可以实现!我们在数组扩展模块 arr_ext -

Only one wish left to fulfill! We write the generic update function in an Array Extensions module, arr_ext -

# arr_ext.py

def update(t, pos, f):
  try:
    return [ *t[:pos], f(t[pos]), *t[pos + 1:]]
  except IndexError:
    return t


高级

我们的 simple 程序以简化的方式解决了该问题.如果我们想解决迷宫 并知道每种解决方案的路径怎么办?让我们在下面编写一个高级程序-

Our simple program solves the problem in a simplified way. What if we want to solve the maze and know the path for each solution? Let's write an advanced program below -

# advanced.py

from maze import maze
from cursor import cursor

def dfs(cursor, maze, path=[]):
  q = maze.get(*cursor)
  if not q or q.is_wall() or q.is_step():
    return
  elif q.is_exit():
    yield (maze, path)
  else:
    next_maze = maze.step(*cursor)
    next_path = [*path, cursor]
    yield from dfs(cursor.up(), next_maze, next_path)
    yield from dfs(cursor.down(), next_maze, next_path)
    yield from dfs(cursor.right(), next_maze, next_path)
    yield from dfs(cursor.left(), next_maze, next_path)

def solve(cursor, maze):
  for x in dfs(cursor, maze):
    return x

请注意,高级解决方案只是对简单模块的一小部分调整.让我们来看看第一个解决的迷宫是什么样的

Notice how the advanced solution is just a small adjustment of the simple module. Let's see what the first solved maze looks like -

# advanced.py (continued)

print(solution(solve(c, m)))

########
#---   #
###-#L #
#  -## #
# #----#
# ####-#
# L   e#
########
(1, 1)->(1, 2)->(1, 3)->(2, 3)->(3, 3)->(4, 3)->(4, 4)->(4, 5)->(4, 6)->(5, 6)

现在让我们看看已解决的迷宫和路径的全部

Now let's see all of the solved mazes and paths -

# advanced.py (continued)

for x in dfs(c, m):
  print(solution(x), end="\n\n")

########
#---   #
###-#L #
#  -## #
# #----#
# ####-#
# L   e#
########
(1, 1)->(1, 2)->(1, 3)->(2, 3)->(3, 3)->(4, 3)->(4, 4)->(4, 5)->(4, 6)->(5, 6)

########
#---   #
###-#L #
#---## #
#-#L   #
#-#### #
#-----e#
########
(1, 1)->(1, 2)->(1, 3)->(2, 3)->(3, 3)->(3, 2)->(3, 1)->(4, 1)->(5, 1)->(6, 1)->(6, 2)->(6, 3)->(6, 4)->(6, 5)

########
#----- #
### #--#
#   ##-#
# #L  -#
# ####-#
# L   e#
########
(1, 1)->(1, 2)->(1, 3)->(1, 4)->(1, 5)->(2, 5)->(2, 6)->(3, 6)->(4, 6)->(5, 6)

########
#----- #
### #--#
#---##-#
#-#----#
#-#### #
#-----e#
########
(1, 1)->(1, 2)->(1, 3)->(1, 4)->(1, 5)->(2, 5)->(2, 6)->(3, 6)->(4, 6)->(4, 5)->(4, 4)->(4, 3)->(3, 3)->(3, 2)->(3, 1)->(4, 1)->(5, 1)->(6, 1)->(6, 2)->(6, 3)->(6, 4)->(6, 5)

########
#------#
### #L-#
#   ##-#
# #L  -#
# ####-#
# L   e#
########
(1, 1)->(1, 2)->(1, 3)->(1, 4)->(1, 5)->(1, 6)->(2, 6)->(3, 6)->(4, 6)->(5, 6)

########
#------#
### #L-#
#---##-#
#-#----#
#-#### #
#-----e#
########
(1, 1)->(1, 2)->(1, 3)->(1, 4)->(1, 5)->(1, 6)->(2, 6)->(3, 6)->(4, 6)->(4, 5)->(4, 4)->(4, 3)->(3, 3)->(3, 2)->(3, 1)->(4, 1)->(5, 1)->(6, 1)->(6, 2)->(6, 3)->(6, 4)->(6, 5)

别忘了实现您的愿望!我们可以看到正在出现一个新的模块 solution ,但是这次我们将其保留在同一文件中-

Don't forget to fulfill your wishes! We can see the emergence of a new module, solution, happening, but this time we will just leave it in the same file -

# advanced.py (continued)

def to_str(t):
  (maze, path) = t
  return str(maze) + "\n" + "->".join(map(str, path))

class solution:
  def __init__(self, t): self.t = t
  def __str__(self): return to_str(self.t)

这篇关于使用Python查找所有迷宫解决方案的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆