不明白这段代码

时间:2011-05-26 23:31:05

标签: f# recursion pattern-matching tail-recursion

我是F#的新手,发现了一些我想使用的代码。此代码采用列表并返回列表的后半部分。我希望有人可以逐行了解它的作用。我想改变它,所以它返回列表的前半部分。这是我的问题之后的代码。

let cut l = 
   let rec cut = function
       | xs, ([] | [_]) -> xs  
       | [], _ -> []
       | x::xs, y::y'::ys -> cut (xs, ys)
   cut (l, l)

= function做了什么?

我非常确定| xs, ([] | [_]) -> xsxs是否有| [], _ -> []将其添加到列表中

我不明白这是做什么| x::xs, y::y'::ys -> cut (xs, ys)

xs:我理解上半部分,它创建了两个子列表,令我困惑的是为什么cut正在发送尾部ys和{{1}}。不剪切只取一个参数吗?

3 个答案:

答案 0 :(得分:5)

该函数返回给定列表的后半部分。

代码的有趣部分只是嵌套(递归)函数,因为外部函数的唯一目的是调用嵌套函数并将指定列表传递两次。嵌套的cut函数有两个参数(作为元组),所以它的类型是:

cut : 'a list * 'a list -> 'a list

当递归调用时,这是被调用的函数(这解释了为什么用两个参数调用它)。这是注释代码:

// The 'function' syntax means that the arguments of the function are matched against 
// several clauses. When the arguments (lists) match the clause, the clause is selected
// and its body will be executed. 
let rec cut = function
  // When the second list is empty or contains a single element,
  // the function return all elements of the first list
  | xs, ([] | [_]) -> xs  
  // When the first list is empty, return empty list
  | [], _ -> []
  // When first list is non-empty and second contains at least two elements,
  // the function takes one element from the first list and two elements from 
  // the second list (x, y, y'), ignores them and calls itself with the 
  // remaining lists as arguments.
  | x::xs, y::y'::ys -> cut (xs, ys)

cut ([ 1 .. 10 ], [ 1 .. 10 ])

该功能的想法是它迭代同一列表的两个副本。在每个递归步骤中,它从第二个元素中跳过两个元素,从第一个元素中跳过一个元素。当它到达第二个列表的末尾时,第一个列表包含列表的后半部分(因为该函数正在将其元素跳过2倍的速度)。

要获取列表的前半部分,您需要在内部递归cut函数中添加其他参数。您可以使用它来累积第一个列表中的元素。同样,您需要以两倍的速度跳过其中一个列表的元素。您不需要跳过其他列表的第一个元素,而是需要将它们添加到累加器中。

我不会给你一个完整的解决方案,但为了给你一些想法,这里是伪代码:

  | x::xs, _::_::ys -> 
      // Call 'cut' recursively to process 'xs' and 'ys'
      // and add the element 'x' to the accumulator.

编写函数的另一种方式是使用match而不是function,并将两个参数写为标准的多个参数(而不是使用元组)。忽略最后一个子句中的元素时,也可以使用_,因为不需要它们的名称:

let rec cut l1 l2 = 
  match l1, l2 with
  | xs, ([] | [_]) -> xs  
  | [], _ -> []
  | _::xs, _::_::ys -> cut xs ys

cut [ 1 .. 10 ] [ 1 .. 10 ]

答案 1 :(得分:1)

更改它以返回列表前半部分的最简单方法是:将反向列表传递给内部函数并反转结果。

let cut l = 
  let rec cut = function
    | xs, ([] | [_]) -> xs  
    | [], _ -> []
    | x::xs, y::y'::ys -> cut (xs, ys)
  let k = List.rev l
  cut (k, k) |> List.rev

没有List.rev

let cut l = 
  let rec cut f = function
    | x::_, [_] -> f [x]
    | _, [] -> f []
    | [], _ -> []
    | x::xs, _::_::ys -> cut (fun acc -> f (x::acc)) (xs, ys)
  cut id (l, l)

答案 2 :(得分:1)

通过实现下面的行

,最简单的方法来查看切割功能正在做什么
| x::xs, y::y'::ys -> cut (xs, ys)

清空第二个列表的速度是第一个列表的两倍。这是因为它从ys列表的头部拉出2个元素,从xs列表的头部拉出一个元素并将它们扔掉。如果它连续执行此操作,则ys将首先终止,而xs将包含原始列表的下半部分。