【问题标题】:How can i make inner recursive function reach original variable in OCaml?如何使内部递归函数达到 OCaml 中的原始变量?
【发布时间】:2020-02-10 19:25:09
【问题描述】:

我正在学习 OCaml,现在卡在了代码上。 这是一个从图表中创建可访问节点列表的代码。

type graph = (vertex * vertex) list
and vertex = int

let rec sort lst =
   match lst with
     [] -> []
   | h::t -> insert h (sort t)
 and insert n lst =
   match lst with
     [] -> [n]
   | h::t -> if n <= h then n :: lst else h :: insert n t;;

let rec remove lst =
match lst with
| []       -> []
| x::[]    -> x::[]
| x::y::tl ->
   if x=y then remove (y::tl)
   else x::remove (y::tl);;
     
let rec reach : graph * vertex -> vertex list
= fun (g, v) ->
  match g with
  |[] -> []
  |h::t ->let x = [] in
          match h with
         |a,b -> if a = v then remove(sort(v::x) @ (reach (g, b)))
                  else
                    remove(sort(v::reach (t, v)));;
reach([(1,2);(2,3);(3,4);(4,2);(2,5)],4);;

我认为由于我缺乏编码能力,我的代码变得毫无意义地复杂。 此外,我现在面临的主要问题是我无法使递归函数“到达”以访问原始列表“g”,因为它在使用列表“t”访问时在 else 条件下递归。

痕迹说

reach <-- ([(1, 2); (2, 3); (3, 4); (4, 2); (2, 5)], 4)
reach <-- ([(2, 3); (3, 4); (4, 2); (2, 5)], 4)
reach <-- ([(3, 4); (4, 2); (2, 5)], 4)
reach <-- ([(4, 2); (2, 5)], 4)
reach <-- ([(4, 2); (2, 5)], 2)
reach <-- ([(2, 5)], 2)
reach <-- ([(2, 5)], 5)
reach <-- ([], 5)
reach --> []
reach --> [5]
reach --> [2; 5]
reach --> [2; 5]
reach --> [4; 2; 5]
reach --> [2; 4; 5]
reach --> [2; 4; 5]
reach --> [2; 4; 5]
- : vertex list = [2; 4; 5]

首先,我使用 let y = g 声明了一个新变量并更改了代码

|a,b -> if a = v then remove(sort(v::x) @ (reach (y, b)))
                  else
                    remove(sort(v::reach (t, v)));;

因为我相信重复项将被有趣的“删除”删除,内部函数将使用列表 y 访问,而不是失去头脑的 t。然而事情确实 不按我的计划去。它仍然给我同样的结果。 要在其他条件下使用原始列表“g”进行函数访问,我该怎么办...?

reach <-- ([(1, 2); (2, 3); (3, 4); (4, 2); (2, 5)], 4)
reach <-- ([(2, 3); (3, 4); (4, 2); (2, 5)], 4)
reach <-- ([(3, 4); (4, 2); (2, 5)], 4)
reach <-- ([(4, 2); (2, 5)], 4)
reach <-- ([(1, 2); (2, 3); (3, 4); (4, 2); (2, 5)], 2)
(*I want function goes back to original list as the variable changes like above*)

【问题讨论】:

    标签: list recursion ocaml


    【解决方案1】:

    你可以定义一个辅助函数:

    let reach g v = 
      let rec aux g' v' = ... (* here you can use g as the auxiliary function defines g' instead *)
      in aux g v
    

    请注意,我还使用两个参数而不是一个元组来定义函数,它更符合习惯:)

    另外,如果v' 总是相同的值,辅助函数不需要重新定义它

    let reach g v = 
      let rec aux g' = ... 
      in aux g v
    

    再说一句。您可以进行更深入的模式匹配,例如:

      match g with
      |[] -> []
      |(a,b)::t -> let x = [] in
         if a = v
         then remove(sort(v::x) @ (reach (g, b)))
         else remove(sort(v::reach (t, v)));;
    

    不需要第二场比赛。

    最后,您可能知道 function 关键字创建一个参数创建模式匹配的函数:

    let f x = match x with
    (* same as *)
    let f = function
    

    因此:

    let reach (g:graph) (v:vertex) =
      let aux v' = function (*we don't create a g' variable here, we match it straight *)
      | [] -> []
      | (a,b)::t as g' -> (* you can still define the variable in the matching if you need it *)
        let x = [] in
          if a = v
          then remove(sort(v::x) @ (aux b g')) (* or g if that's what you need *)
          else remove(sort(v::aux v t))
      in aux v g
    

    会和你的代码一样

    编辑:通过对aux 的递归调用更正了对reach 的递归调用,因为它不起作用。

    【讨论】:

    • PS:当我阅读更多时,我发现您的remove 函数是重复数据删除函数,名称有点误导。 reach 中的 x 这个名字也不清楚你的意图
    • 正如你所提供的,我尝试了 aux 函数并将 (a, b):: 定义为 g'。我希望在“到达”功能继续时,if 语句中的 g' 不会改变,但我仍然得到相同的结果,#trace 也给了我相同的过程。 “会和你的代码做同样的事情”是不是意味着我会按照我的意愿去做......?
    • g' 在每次调用aux 时都会有所不同。实际上我忘记在代码中调用aux 这是我的一个错误让我纠正。我尝试编写与您提供的代码完全相同的代码,因为我没有对您尝试实现的算法做出假设:)
    • 我注意到的一件事是变量 x。您是否打算在所有递归调用中保留相同的列表?在每次递归调用时对其进行变异?
    【解决方案2】:

    直接回答您的问题,这里是如何在递归函数中访问原始值

    let to_matrix outer = 
        let rec loop = function
          | [] -> []
          | x :: xs -> [x::outer] @ loop xs in
        loop outer
    

    此函数将遍历列表的每个元素并创建一个列表列表,其中每个元素都是由原始元素组成的列表,该原始元素附加到整个原始列表中,例如,

    # to_matrix [1;2;3];;
    - : int list list =
    [[1; 1; 2; 3]; [2; 1; 2; 3]; [3; 1; 2; 3]]
    

    这个例子可能很愚蠢,但我希望它能阐明技术。这里的想法是我们创建一个内部函数,在我们的例子中命名为loop,它有自己的参数,我们只在这个参数上递归,同时保持原始outer 列表不变。

    虽然这是一种惯用的方法,但通常不需要创建内部函数或辅助函数。真正需要的是一个额外的参数,因此最终我们将有两个参数 - 一个随着每次迭代而减小,另一个保持不变,例如,

    let rec to_matrix outer inner = match inner with
      | [] -> []
      | x :: xs -> [x::outer] @ to_matrix outer xs
    

    这种方法的唯一警告是,现在我们必须通过两次列表,例如,

    to_matrix [1;2;3] [1;2;3];;
    

    这就是习惯用更好的界面隐藏此功能的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-12
      • 2012-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-30
      • 1970-01-01
      相关资源
      最近更新 更多