如何从OCaml中的for循环中获取值
我编写了以下代码,从列表中随机选择项目n个项目,并将所选内容放入新列表中 代码如下如何从OCaml中的for循环中获取值,ocaml,Ocaml,我编写了以下代码,从列表中随机选择项目n个项目,并将所选内容放入新列表中 代码如下 let random_extract list n = let l = ((List.length list)- 1) and acc = [] in for i = 1 to n do (List.nth list (Random.int l) ) :: acc (* Line 447 *) done; acc ;; 当我加载包含此代码的文件时
let random_extract list n =
let l = ((List.length list)- 1)
and acc = [] in
for i = 1 to n do
(List.nth list (Random.int l) ) :: acc (* Line 447 *)
done;
acc
;;
当我加载包含此代码的文件时,出现以下错误
File "all_code.ml", line 447, characters 2-40:
Warning 10: this expression should have type unit.
val random_extract : 'a list -> int -> 'b list = <fun>
文件“all_code.ml”,第447行,字符2-40:
警告10:此表达式的类型应为unit。
val random_extract:'a list->int->'b list=
两个问题,,
问题1:这个警告是什么意思
其次,当我运行这个函数时,我不会得到预期的列表,而是一个空列表
问题2:如何从for循环内部获取acc的值?在我看来,您已经开始使用OCaml,并且具有必要的编程背景。考虑OCaml(以及一般的函数式编程)的最佳建议可能是开始将所有程序构造都看作表达式,即具有值的东西。在命令式编程中,许多事情(如for循环、while循环、if语句)没有值,它们只是“做”事情。在函数式编程中,一切都有价值 因为OCaml不是一种纯函数式语言,所以保留了一个值来表示一个表达式,它只是“做”了一些事情。值是这样写的:
()
。此值的类型称为单位
(它是单位
类型的唯一值)
因为for
循环的目的是“做”事情,即使在OCaml中,循环中的表达式也应该具有typeunit
。但是你写了一些列表类型的东西。任何看起来像x::y
的东西都必须是一个列表,因为:
构造函数创建了一个列表。这就是编译器警告你的。循环的内部应该有类型unit
,但它有一个列表类型
第二个问题是表达式x::y
不会更改y
的值。这是函数式编程的本质。表达式只需计算一个新值(列表)。它不会更改表达式中显示的任何值。因此,acc
的值与开始时的值相同
我真的建议您使用递归(如Basile Starynkevitch所建议的)来解决这个问题,而不是尝试使用命令式结构,比如
for
循环。它将产生更加惯用的OCaml代码。如果出于某种原因确实需要使用for
循环,则需要将acc
变量设置为可变值,并且需要在循环中对其进行变异(更改)。Jeffrey Scofield所说的,以及:
let random_extract list n =
let l = ((List.length list)- 1)
and acc = ref [] in
for i = 1 to n do
acc := (List.nth list (Random.int l) ) :: !acc (* Line 447 *)
done;
!acc
如果要将for
循环用于类似的内容,则acc
必须是一个引用,即一个可变值。在Ocaml和ML中,通常让x=…
定义一个不可变的值。接下来,当您编写(List.nth List(Random.int l))::acc
时,它的意思是“看,我可以制作一个列表”。您没有说过新构造的列表必须分配给任何对象,特别是它没有分配给acc
(它不能,因为acc
是不可变的)。Ocaml发出了警告,因为它看到for
循环的主体生成了一个列表,但Ocaml知道for
循环的主体应该生成一个单元(C/C++/Java mis术语中的“void”)
像那样使用for
循环是很难看的。正确的方法是这样做:
let random_extract lst =
let l = List.length lst - 1 in
let rec extract = function
| 0 -> []
| n -> List.nth lst (Random.int l) :: extract (n - 1)
in
extract
如果您是Ocaml新手,您真的应该花时间完全理解上面的代码。之后,您应该学习以下代码,并确保理解为什么第一个版本(查找“尾部递归”)更有效:
下面是我如何回答这个问题的
let rec remove_at_k list i acc =
match list with
[] -> []
| h::t -> if (i = 1) then acc @ t else remove_at_k t (i-1) (acc@[h])
;;
let random_extract lst n =
let rec extract lst2 acc = function
| 0 -> acc
| n -> let l = List.length lst2 in let index = (Random.int(l)) in
(* print_list lst2; Printf.printf " with I %d\n" index ; *)
extract (remove_at_k lst2 (index+1) []) (List.nth lst2 index :: acc) (n-1)
in
extract lst [] n
;;
我不能使用上面Andrej Bauer提供的解决方案,因为该解决方案不能保证同一元素不会被选中两次。上面的解决方案可以做到这一点。在带有
for
循环和list的列表上迭代。nth
的味道很差,效率也很低。为什么不编写一个真正的尾部递归呢?您的解决方案可能会导致同一个元素被随机选择多次。我创建了一个新的解决方案,一旦元素被选中,它就会删除。你的也可以。我只是在模仿你所做的。你没有说过重复是不允许的。不管怎样,把你的代码放进去,如果你愿意,我们可以对它进行评论。顺便说一下,你的算法是二次的,我不知道如何提高时间复杂度。如果使用数组而不是列表,我们可以使其成为线性时间。
let rec remove_at_k list i acc =
match list with
[] -> []
| h::t -> if (i = 1) then acc @ t else remove_at_k t (i-1) (acc@[h])
;;
let random_extract lst n =
let rec extract lst2 acc = function
| 0 -> acc
| n -> let l = List.length lst2 in let index = (Random.int(l)) in
(* print_list lst2; Printf.printf " with I %d\n" index ; *)
extract (remove_at_k lst2 (index+1) []) (List.nth lst2 index :: acc) (n-1)
in
extract lst [] n
;;