如何解决OCaml 递归函数:子列表元素乘以其在列表中的位置,然后求和
我正在尝试创建一个函数,该函数将一个 int 列表作为参数,并返回一个 int 与其在列表中的位置之间的乘积之和。举个例子:multSum [5; 11; 15] 应该返回 (5 * 1 + 11 * 2 + 15 * 3) = 72。
它应该以递归方式编写,我正在尝试避免使用 List.map 或 List.filter 或任何其他预制函数。
通过划分和控制上面的查询,到目前为止,我已经开始尝试以下操作:
let rec tir f acc l =
match l with
|[] -> acc
|h::t -> tir f (f acc h) t ;;
val tir : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>
然后我转向了这个:
let rec carto f a b =
match (a,b) with
|([],[])->([])
|(h1::t1,h2::t2)->(f h1 h2):: (carto f t1 t2)
|_->invalid_arg "carto";;
val carto : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list = <fun>
最终的想法是:
let prod arg1 arg2 =
tir (+) 1 (carto ( * ) arg1 arg2);;
val prod : int list -> int list -> int = <fun>
但我现在被困住了,我不确定从现在开始的方向。我想尝试在 "l" 中搜索索引并替换 acc 中的每个索引 int,以使其工作,但恐怕我让事情变得复杂了......有什么帮助吗?
编辑 1 :
let rec multSum l =
let rec indices n xs = match xs with
| [] -> []
| h::t -> n::(indices (n+1) t)in
let rec tir f acc l =
match l with
|[] -> acc
|h::t -> tir f (f acc h) t in
let rec carto f a b =
match (a,b) with
|([],[])->([])
|(h1::t1,h2::t2)->(f h1 h2):: (carto f t1 t2)
|_->invalid_arg "carto" in
let prod arg1 arg2 =
tir (+) 0 (carto ( * ) arg1 arg2) in
prod l (indices 1 l);;
val multSum : int list -> int = <fun>
根据您的回复,这些肯定是重写了“折叠”和“地图”。至少,我现在确定我走在正确的轨道上。我已经按照上述编辑 1 中的指示将整个代码组合在一起。
它似乎运行良好......我知道我想要一个递归函数,它就在这里。但是,您认为它可以在更短的时间内递归地完成吗?
解决方法
你的 tir
函数看起来像一个折叠;实际上与 List.fold_left
具有完全相同的类型:
# List.fold_left;;
- : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>
在以下代码段中,prod
函数看起来像一个 map2
# List.map2;;
- : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list = <fun>
您可以使用折叠和映射来计算您想要的函数,但您还需要首先从值列表构建索引列表。您可以按如下方式执行此操作:
let rec indices n xs = match xs with
| [] -> []
| h::t -> n::(indices (n+1) t);;
例如:
# indices 1 [5;1;3];;
- : int list = [1; 2; 3]
这不是递归终结符,如果你先计算列表的长度,你会如何以递归终结符的方式构建列表?
然后您应该能够在列表 prod
和辅助列表 xs
上调用 indices 1 xs
。这有点浪费,因为您需要构建一个辅助列表,但我认为它很容易理解,像 map 或 fold 这样的高阶函数可以整体工作列表,因此需要考虑的极端情况更少。
但是,在走更抽象的路线之前,最好先为您的特定问题编写一个直接递归函数。
直接递归函数也不需要额外的内存分配。如果您编写递归终端函数,您将携带额外的累加器值:
- 列表中的当前位置,最初为 1
- 当前的乘积总和,初始为 0
那么,您的函数具有以下骨架:
let rec f xs index product = match xs with
| [] -> ...
| h::t -> ...
您可以将其包装在主函数 g
中:
let g xs = f xs 1 0;;
,
@coredump 说得很对,这看起来像是折叠的理想场景,但额外的功能并不是那么必要。我们可以只使用一个元组来传递索引和求和信息,然后当我们完成时,从元组中丢弃索引信息。
let sum_list_prod lst =
let (_,result) = List.fold_left
(fun (i,sum) x -> (i + 1,sum + i * x))
(1,0)
lst
in
result
编辑:左折叠的一个简单实现,用于演示此处进行的递归。
let rec foldl f init lst =
match lst with
| [] -> init
| first :: rest -> foldl f (f init first) rest
所以通过一个简单的例子使用 sum_list_prod
:
sum_list_prod [2; 3; 4]
像这样调用折叠:
List.fold_left (fun (i,sum + i * x)) (1,0) [2; 3; 4]
正如它所评估的那样:
List.fold_left (fun (i,0) [2; 3; 4]
List.fold_left (fun (i,sum + i * x)) (2,2) [3; 4]
List.fold_left (fun (i,sum + i * x)) (3,8) [4]
List.fold_left (fun (i,sum + i * x)) (4,20) []
(4,20)
然后我们扔掉 4
,因为我们不再需要它,只剩下 20
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。