如何在函数中分支多种可能性?

时间:2016-02-11 19:11:40

标签: f#

一直试图解决Project Euler problem 15(需要查看网站上的图表来理解它)。

我知道有数学方法可以解决这个问题,但我有兴趣找到一种使用直接探索的方法。我的想法是从左上角开始,然后使用递归函数来分支每个可能的选项(即向右或向下,只要你不在边缘),返回一个可能的路径列表。 / p>

我的问题是我没有看到如何处理你可以分支的情况。到目前为止,我想出了以下内容......

let findPaths n =
  let rec path n x y p =
    if x < 1 || x > n || y < 1 || y > n then failwith (sprintf "Invalid position (%d, %d)" x y)
    match (x, y) with
    | (_, _) when x = n && y = n ->
      printfn "Done"
      (x, y)::p
    | (_, _) when x = n && y < n ->
      printfn "At (%d, %d), can only move down" x y
      (x, y)::path n x (y + 1) p
    | (_, _) when x < n && y = n ->
      printfn "At (%d, %d), can only move right" x y
      (x, y)::path n (x + 1) y p
    | (_, _) ->
      printfn "At (%d, %d), can move right or down" x y
      (x, y)::path n (x + 1) y p
  path n 1 1 []

printfn就在那里,所以我可以看到它是如何通过网格的。

这将找到一条路径,即向右移动直到它到达边缘,然后向下移动到网格的末尾。

我想做的是让最后一个案例探索两种可能性,返回一个路径列表。这样,它最终将返回所有路径。我不知道怎么做。

任何想法?我甚至可以这样做吗?如果没有,你还会怎么解决这个问题?同样,我正在寻找一种探索网格的解决方案,因此理论上可以在缺少边缘的网格上使用,其中简单的数学方法不起作用。

1 个答案:

答案 0 :(得分:4)

我发现一个非常好的解决方案是更改path函数,使其返回一系列路径而不是单个路径。这可以通过将正文包裹在seq { .. }内并使用yield关键字生成结果来完成。好消息是它不会非常改变您的代码:

let findPaths n =
  let rec path n x y p = seq {
    if x < 1 || x > n || y < 1 || y > n then failwith (sprintf "Invalid position (%d, %d)" x y)
    match (x, y) with
    | (_, _) when x = n && y = n ->
      printfn "Done"
      yield (x, y)::p
    | (_, _) when x = n && y < n ->
      printfn "At (%d, %d), can only move down" x y
      for subPath in path n x (y + 1) p do
        yield (x, y)::subPath
    | (_, _) when x < n && y = n ->
      printfn "At (%d, %d), can only move right" x y
      for subPath in path n (x + 1) y p do
        yield (x, y)::subPath
    | (_, _) ->
      printfn "At (%d, %d), can move right or down" x y
      for subPath in path n x (y + 1) p do
        yield (x, y)::subPath
      for subPath in path n (x + 1) y p do
        yield (x, y)::subPath }
  path n 1 1 []

一个棘手的问题是,现在需要在递归调用path时迭代所有可能的子路径 - 然后在每个子路径的开头附加当前的(x, y)。 / p>

有一些重复,因为您现在需要代码在两个不同的位置向下移动/向右移动。我认为使用普通的if实际上更容易编写 - 当你没有模式匹配时,使用match没有多大好处:

let findPaths n =
  let rec path n x y p = seq {
    if x < 1 || x > n || y < 1 || y > n then 
      failwith (sprintf "Invalid position (%d, %d)" x y)
    if x = n && y = n then yield (x, y)::p
    if y < n then
      for subPath in path n x (y + 1) p do
        yield (x, y)::subPath
    if x < n then
      for subPath in path n (x + 1) y p do
        yield (x, y)::subPath }
  path n 1 1 []

另外,我认为你开始使用一个使用累加器参数p的解决方案,但是你放弃了它,而不是在你去的时候构建路径,你正在构建它,因为你从递归调用。使用p可以使代码更加出色:

let findPaths n =
  let rec path n x y p = seq {
    if x < 1 || x > n || y < 1 || y > n then 
      failwith (sprintf "Invalid position (%d, %d)" x y)
    if x = n && y = n then yield List.rev ((x, y)::p)
    if y < n then
      yield! path n x (y + 1) ((x, y)::p)
    if x < n then
      yield! path n (x + 1) y ((x, y)::p) }
  path n 1 1 []