实现F#回溯

时间:2013-07-03 22:22:08

标签: f# implementation backtracking

我一直在玩一个着名的背包问题。这一次 - 然而 - 我决定在F#中实现它,因为我正在学习语言并且发现它特别有趣。

我已经成功实现了算法的主要部分,但我也需要回溯。不幸的是,我在网上找到的所有信息都使用了Haskell,我不知道(还有))。

作为占位符,我使用可变变量实现了回溯 (没有详细说明,“组合(i,k)”返回i项的部分解和k的容量:

let output = 
    List.rev
        [
            let i = ref N
            let k = ref K
            // analize items starting with the last one
            for item in Array.rev items do
                match !k - item.Size with
                // if size of i-th item is greater than current capacity...
                | x when x <  0 -> i := !i - 1
                       // ... this means we haven't taken this item
                                   yield 0
                // otherwise we've got two cases
                | x when x >= 0 -> let v1 = combinations (!i-1, !k) id
                                   let vc = combinations (!i, !k) id
                                   if v1 = vc then 
                                    // case 1: item hasn't been taken
                                    i := !i - 1
                                    yield 0
                                   else
                                     // case 2: item is contained in the optimal solution
                                    i := !i - 1
                                    k := x
                                    yield 1
        ]

List.iter (fun x -> printf "%A " x) output

我相信在F#中有更好的方法(例如使用计算表达式)。我很高兴听到有关如何以功能方式实现此功能的任何提示/信息。

最后一件事:请记住我是函数式编程的新手,并试图避免像我发现的那样神奇的表达式:

“monad是endofunctors类别中的幺半群,问题是什么?” :)

1 个答案:

答案 0 :(得分:6)

在这种情况下您可以使用的一般模式是折叠。当您需要遍历列表(例如items,在您的情况下)并且保持一些状态(在您的情况下为ik)时,这非常有用。这可以使用高阶函数Array.fold来实现(当您使用数组时):

let results, _, _ = items |> Array.rev |> Array.fold (fun (resultsSoFar, i, k) item ->
  match k - item.Size with
  // if size of i-th item is greater than current capacity...
  | x when x <  0 -> 
      // ... this means we haven't taken this item
      (0::resultsSoFar, i - 1, k)
  // otherwise we've got two cases
  | x when x >= 0 -> 
      let v1 = combinations (i-1, !k) id
      let vc = combinations (i, !k) id
      if v1 = vc then 
        // case 1: item hasn't been taken
        (0::resultsSoFar, i - 1, k)
      else
        (1::resultsSoFar, i - 1, x) ) ([], N, K)

这个想法是函数获得先前的状态(resultsSoFar, i, k)和当前的item并且应该返回新状态 - 例如,如果我们想要生成0并递减{{1} },我们可以返回i,如第一种情况所示。

初始状态是最后一个参数(0::resultsSoFar, i - 1, k),你得到的结果包含所有三个值,所以我使用模式([], N, K)来忽略results, _, _的最后一个值和{ {1}}。

请注意,您的代码不完整,因此我无法运行代码段(可能存在错误!)但我希望它能够证明一般的想法。您也可以使用递归函数或递归序列表达式来实现相同的功能(在这两种情况下,您都会将状态保存在参数中,并且您可以在列表上使用模式匹配来处理空白或非空时的情况空)。

使用递归序列表达式的方法可能更接近您的命令式版本:

i