如何从OCaml中的for循环中获取值

如何从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 ;; 当我加载包含此代码的文件时

我编写了以下代码,从列表中随机选择项目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
;;
当我加载包含此代码的文件时,出现以下错误

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中,循环中的表达式也应该具有type
unit
。但是你写了一些列表类型的东西。任何看起来像
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
;;