如何递归识别网格中特定类型的单元格?
How to recursively identify cells of specific type in grid?
我正在学习 F# 并且正在构建一个扫雷应用程序。作为其中的一部分,我正在尝试一种方法,如果地雷被引爆,则递归地引爆所有相邻的地雷。所以如果我有一个像这样的网格:
| 0 | 1 | 2 |
------------------------
0 | E-1 | M | E-2 |
1 | E-2 | E-4 | M |
2 | M | E-3 | M |
然后我在 0,1 引爆地雷,它会依次引爆 1,2 的地雷,然后引爆 2,2 的地雷。 2,0 中的地雷不会引爆,因为它不与其他地雷相邻。
此时我实际上将该字段实现为列表:
module CellContents =
type T =
| Empty of int
| Mine
| Crater
module Location =
type T = { Row: int; Column: int }
module Cell =
type T =
{ Content : CellContents.T
Location : Location.T }
module Field =
type T = Cell.T list
我遇到的问题是如何从单元格 0,1 开始并以包含所有相邻地雷的列表结束。所以,我需要一个列表(只显示坐标):
let minesToDetonate = [ {Row=0;Column=1};{Row=1;Column=2};{Row=2;Column=2} ]
我可以毫不费力地获取特定位置的相邻地雷,然后从该组中确定地雷。
我遇到的麻烦 是如何让它以某种方式递归,直到附近没有发现地雷,然后给我一份我需要引爆的地雷的主列表。
一旦我得到地雷的主列表,我就可以引爆它们并建立一个更新的区域,让那些地雷变成弹坑。
更新
@Kevin 的回答有效,但我很难理解它。如果其他人也遇到困难,我将在下面添加功能,并进行评论和一些更改。
let detonateProximity (field:T) (start:Cell.T) =
let rec inner cells m acc =
match cells with
| [] -> acc
| x::xs ->
match x.Content with
|Mine ->
match proximity m.Location x.Location with
// Continue but don't accumulate
| Self -> inner xs m acc
| Near ->
// See if current cell has already been found
match acc |> List.tryFind (fun t -> t = x) with
// If it has, we're done. Pass out
// an empty list which ends recursion.
|Some _ -> []
// If it wasn't found (this parts hurts my brain)...
// calls function once for rest field and then
// using new starting point on whole field.
// Is this efficient at all?
|None -> List.concat [(inner xs m (x::acc));(inner field x (x::acc))]
// Don't accumulate, continue with rest of mines.
| Far -> inner xs m acc
// Not a mine, keep going, don't accumulate
|_ -> inner xs m acc
// The set ensures no duplicates
Set.ofList (inner field start [])
proximity
函数(未显示)简单地包含了确定被测矿山是否为参考矿山、接近或远离参考矿山的逻辑。例如。 Self
如果当前单元格和地雷之间的 距离 为零,则返回 Self
{Row=0, Column=0}。
好的,所以我不会 F#,所以我打算在 Python 中写这个。
def getDetonationList ( startingWithThisCell ) :
seen = set()
current = set ( [startWithThisCell] )
while current :
# Get all the mine cells that are adjacent to every ones
# we are currently exploring. Then remove the ones we're
# currently exploring and the ones we've seen, just so
# we don't duplicate effort later.
nearby = allMinesAdjacentToCells(current) - seen - current
# Mark that we've seen the current ones (add the current
# ones to the overall seen ones
seen.update ( current )
# Now we start over, starting from the newly found mines
current = nearby
# Note that if this is empty, this list will end, which
# means that we will have seen as many as we could have
# seen.
return seen
这将 return 一组所有引爆的电池,包括启动连锁反应的电池。
module Location =
type T = {Row: int; Column: int }
let subtract l1 l2 =
{Row=l1.Row - l2.Row;Column=l1.Column-l2.Colum
let detonate (field:Field.T) (start:Cell.T) =
let rec inner cells m acc =
match cells with
| [] -> acc
| x::xs -> match x.Content with
|Mine ->match subtract m.Location x.Location with
|{Row = 0;Column = 0} -> inner xs m acc
|loc when abs (loc.Row) < 2 && abs (loc.Column) < 2 ->
match acc |> List.tryFind (fun t -> t = x) with
|Some _ -> []
|None -> List.concat [(inner xs m (x::acc));(inner field x (x::acc))]
| _ -> inner xs m acc
|_ -> inner xs m acc
Set.ofList (inner field start [])
如果您只想要一个像您问题中那样的位置列表,那么可以像这样轻松转换:
detonate board {Content=Mine;Location={Row=0;Column=1}}
|> Set.map (fun t -> t.Location)
|> Set.toList
我正在学习 F# 并且正在构建一个扫雷应用程序。作为其中的一部分,我正在尝试一种方法,如果地雷被引爆,则递归地引爆所有相邻的地雷。所以如果我有一个像这样的网格:
| 0 | 1 | 2 |
------------------------
0 | E-1 | M | E-2 |
1 | E-2 | E-4 | M |
2 | M | E-3 | M |
然后我在 0,1 引爆地雷,它会依次引爆 1,2 的地雷,然后引爆 2,2 的地雷。 2,0 中的地雷不会引爆,因为它不与其他地雷相邻。
此时我实际上将该字段实现为列表:
module CellContents =
type T =
| Empty of int
| Mine
| Crater
module Location =
type T = { Row: int; Column: int }
module Cell =
type T =
{ Content : CellContents.T
Location : Location.T }
module Field =
type T = Cell.T list
我遇到的问题是如何从单元格 0,1 开始并以包含所有相邻地雷的列表结束。所以,我需要一个列表(只显示坐标):
let minesToDetonate = [ {Row=0;Column=1};{Row=1;Column=2};{Row=2;Column=2} ]
我可以毫不费力地获取特定位置的相邻地雷,然后从该组中确定地雷。
我遇到的麻烦 是如何让它以某种方式递归,直到附近没有发现地雷,然后给我一份我需要引爆的地雷的主列表。
一旦我得到地雷的主列表,我就可以引爆它们并建立一个更新的区域,让那些地雷变成弹坑。
更新 @Kevin 的回答有效,但我很难理解它。如果其他人也遇到困难,我将在下面添加功能,并进行评论和一些更改。
let detonateProximity (field:T) (start:Cell.T) =
let rec inner cells m acc =
match cells with
| [] -> acc
| x::xs ->
match x.Content with
|Mine ->
match proximity m.Location x.Location with
// Continue but don't accumulate
| Self -> inner xs m acc
| Near ->
// See if current cell has already been found
match acc |> List.tryFind (fun t -> t = x) with
// If it has, we're done. Pass out
// an empty list which ends recursion.
|Some _ -> []
// If it wasn't found (this parts hurts my brain)...
// calls function once for rest field and then
// using new starting point on whole field.
// Is this efficient at all?
|None -> List.concat [(inner xs m (x::acc));(inner field x (x::acc))]
// Don't accumulate, continue with rest of mines.
| Far -> inner xs m acc
// Not a mine, keep going, don't accumulate
|_ -> inner xs m acc
// The set ensures no duplicates
Set.ofList (inner field start [])
proximity
函数(未显示)简单地包含了确定被测矿山是否为参考矿山、接近或远离参考矿山的逻辑。例如。 Self
如果当前单元格和地雷之间的 距离 为零,则返回 Self
{Row=0, Column=0}。
好的,所以我不会 F#,所以我打算在 Python 中写这个。
def getDetonationList ( startingWithThisCell ) :
seen = set()
current = set ( [startWithThisCell] )
while current :
# Get all the mine cells that are adjacent to every ones
# we are currently exploring. Then remove the ones we're
# currently exploring and the ones we've seen, just so
# we don't duplicate effort later.
nearby = allMinesAdjacentToCells(current) - seen - current
# Mark that we've seen the current ones (add the current
# ones to the overall seen ones
seen.update ( current )
# Now we start over, starting from the newly found mines
current = nearby
# Note that if this is empty, this list will end, which
# means that we will have seen as many as we could have
# seen.
return seen
这将 return 一组所有引爆的电池,包括启动连锁反应的电池。
module Location =
type T = {Row: int; Column: int }
let subtract l1 l2 =
{Row=l1.Row - l2.Row;Column=l1.Column-l2.Colum
let detonate (field:Field.T) (start:Cell.T) =
let rec inner cells m acc =
match cells with
| [] -> acc
| x::xs -> match x.Content with
|Mine ->match subtract m.Location x.Location with
|{Row = 0;Column = 0} -> inner xs m acc
|loc when abs (loc.Row) < 2 && abs (loc.Column) < 2 ->
match acc |> List.tryFind (fun t -> t = x) with
|Some _ -> []
|None -> List.concat [(inner xs m (x::acc));(inner field x (x::acc))]
| _ -> inner xs m acc
|_ -> inner xs m acc
Set.ofList (inner field start [])
如果您只想要一个像您问题中那样的位置列表,那么可以像这样轻松转换:
detonate board {Content=Mine;Location={Row=0;Column=1}}
|> Set.map (fun t -> t.Location)
|> Set.toList