使用递归的一个更好的方法是让它更通用:而不是找到第一个非零的 3,让我们找到第n
一个非零。
下面是一个OCaml的实现,我不知道Reason。
let rec find_n l n ~f =
if n = 0 then []
else match l with
| [] -> failwith "Cannot find enough items"
| h::t ->
if f h then
h :: (find_n t (n-1) ~f)
else
find_n (t n ~f)
这是函数的签名:
val find_n : 'a list -> int -> f:('a -> bool) -> 'a list = <fun>
这里发生了很多事情,但并没有那么糟糕。这里的逻辑如下:
如果我想要列表中的 3 个项目:
- 如果它的头部匹配,那么我需要在尾部寻找 2 个项目。
- 否则,我仍然需要在尾部寻找 3 个项目。
其他一切都只是附加逻辑:
- 如果我正在寻找 0 个项目,我就完成了。
- 如果列表是空的,我还需要寻找更多的项目,我失败了。
关于尾递归的一句话
不幸的是,上面的实现不是尾递归的,这意味着它在大列表上会失败。使函数尾递归的一种常用技术是使用累加器(acc
在下面的代码中)来存储中间结果。
然后,我们将这个额外的逻辑封装在一个本地辅助函数(通常称为loop
or aux
)中,这样累加器就不会出现在函数的签名中。
let find_n l n ~f =
let rec loop l' n' acc =
if n' = 0 then acc else
match l' with
| [] -> failwith "Cannot find enough items"
| h::t ->
if f h then
loop t (n' - 1) (h::acc)
else
loop t n' acc
in
loop l n []