Recursion 如何在网格中递归地识别特定类型的单元?
我正在学习F#,我正在构建一个扫雷舰应用程序。作为其中的一部分,我正在尝试一种方法,如果一枚地雷被反复引爆,我将引爆所有相邻的地雷。所以如果我有一个网格,比如:Recursion 如何在网格中递归地识别特定类型的单元?,recursion,f#,tail-recursion,Recursion,F#,Tail Recursion,我正在学习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中的地雷不会引爆
| 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 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 [])
接近
功能(未显示)简单地封装了确定被测水雷是参考水雷、靠近参考水雷还是远离参考水雷的逻辑。例如,如果当前单元格和我的单元格之间的距离为零{Row=0,Column=0},则返回Self
。好的,所以我不知道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
这将返回一组所有引爆的电池,包括启动连锁反应的电池
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 [])
好的,否决这一切你想要的,但是Python也是一种编写伪代码的优秀语言,它向你展示了如何做事情的途径,即使它没有复制和粘贴代码。@FoggyFinder,因为我不知道F#,但是“我如何递归地查找附近的单元格”不是一个语言问题:这是一个方法论问题(除非你用的是愚蠢的语言)@iAdjunct,虽然你认为这不一定是一个语言问题,但这是一个与如何从功能上实现这一点相关的问题。你的例子不是从功能上实现的,而是必须实现的。也就是说,你有一个变量,
seen
,你正在循环中更新。在功能性范式中,不会有循环,因为理想情况下,我不会更新变量,而是计算一个值。这是学习F#(或任何函数式编程语言)的重点:学会以不同的方式思考问题。啊,我不知道F#是一种函数语言。我想这就解释了“F”。为什么你不想使用2D数组而不是位置和单元格?好吧,最初的原因是努力工作列表操作,因为我猜在现实生活中我会使用它而不是数组。我还想知道如何使用F一个2d数组解决方案是可行的。我的理解是,如果我保持字段不变,我必须一直重新分配字段,或者我必须更改数组状态(即更改数组中的值)。我愿意接受关于这个和/或澄清的想法。主要的是,这个项目是为了学习如何使用F#,所以我有时会根据我将学到的东西而不是效率来做决定。谢谢!但是,因为我对函数式编程还是很陌生,我的大脑即将爆炸,试图摸索逻辑=:-o。你有没有可能d一些注释来解释发生了什么?我真的不理解tryFind
匹配以及为什么在匹配为None
时更新累加器。
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