将ocaml函数转换为流

时间:2013-04-16 02:09:23

标签: stream ocaml

说我有以下OCaml代码块:

let genblocks () =
  let blocks = ref [] in
  let rec loop depth block =
    let iter i = loop (depth - 1) (i :: block) in
    match depth with
      | 0 -> blocks := block :: !blocks
      | _ -> List.iter iter [1;2;3;4]
  in
  loop 4 [];
  !blocks

这会生成一个列表列表:[[1;1;1;1]; [1;1;1;2]; ...; [4;4;4;4]]

现在,我想将其转换为流(使用Stream模块或类似的东西)。但是,我仍然不知道如何在保持当前代码的整体递归结构的同时做到这一点。我想维护这种结构的原因是它使我能够在生成过程中轻松删除包含某些属性的列表。例如,使用此代码结构,在生成期间,我可以轻松地删除包含子列表[1;1]的列表。 (以上只是一个玩具示例,我的实际应用程序更复杂......)。

有关如何将上述代码转换为“流式”实例的任何提示/想法/指示?一旦达到零深度,我就无法“回溯”......

编辑:另一种查看问题的方法:有没有办法转换genblocks以摆脱列表引用?这似乎是使其与流兼容所需的第一步。

感谢。

3 个答案:

答案 0 :(得分:3)

我的答案中有三个不同的东西:

  1. 演示通用技术以摆脱你的可变变量

  2. 一种特定于算法的技术,可以很容易地生成流

  3. 指向将任何制作人转变为点播流的通用技术的链接

  4. 首先,让我们在枚举的基础上使您的算法通用:

    let genblocks n =
      (* base = [1; ... ; n] *)
      let base = Array.to_list (Array.init n (fun i -> i+1)) in
      let blocks = ref [] in
      let rec loop depth block =
        let iter i = loop (depth - 1) (i :: block) in
        match depth with
          | 0 -> blocks := block :: !blocks
          | _ -> List.iter iter base
      in
      loop n [];
      !blocks
    

    不看代码现在做什么,有一个非常简单的方法 摆脱枚举的方法:转换A -> B类型的任何函数 使用类型为C的可变类型到接收状态的类型A * C -> B * C的函数中,并返回其修改后的值 - 这就是所谓的“国家monad”。所以我只想添加一个 您的函数blocksloop的附加参数iter,以及 让它不返回unit而是int list list

    let genblocks n =
      let base = Array.to_list (Array.init n (fun i -> i+1)) in
      let rec loop depth blocks block =
        let iter blocks i = loop (depth - 1) blocks (i :: block) in
        match depth with
          | 0 -> block :: blocks
          | _ -> List.fold_left iter blocks base
      in
      loop n [] []
    

    现在让我们来看看这个算法到底做了什么:

    # genblocks 3;;
    - : int list list =
    [[3; 3; 3]; [2; 3; 3]; [1; 3; 3]; [3; 2; 3]; [2; 2; 3]; [1; 2; 3]; [3; 1; 3];
     [2; 1; 3]; [1; 1; 3]; [3; 3; 2]; [2; 3; 2]; [1; 3; 2]; [3; 2; 2]; [2; 2; 2];
     [1; 2; 2]; [3; 1; 2]; [2; 1; 2]; [1; 1; 2]; [3; 3; 1]; [2; 3; 1]; [1; 3; 1];
     [3; 2; 1]; [2; 2; 1]; [1; 2; 1]; [3; 1; 1]; [2; 1; 1]; [1; 1; 1]]
    

    当使用参数3调用时(在代码4中是硬编码的),这个 算法返回数字1,2和1的所有3种组合 除此之外,它列举了所有三位数字 基数为3的数字系统(使用1到3之间的数字代替 像往常一样0和2)。

    有一种非常简单的方法可以枚举您所学的数字 学校:从一个数字到下一个,简单地增加 (或减少)它。在您的情况下,列表以“大”数字开头 然后去“小”,所以我们要减少。随着 你的基数是[1; N]而不是[0; N-1],减少 功能写得

    let decr n block =
      let rec decr n = function
        | [] -> raise Exit
        | 1::rest -> n :: decr n rest
        | i::rest -> (i - 1) :: rest
      in try Some (decr n block) with Exit -> None
    

    当我们达到0(在你的系统中,[1; 1; 1 ..])时,我让它返回None 此时很容易停止枚举。

    decr 3 [3;3;3];;
    - : int list option = Some [2; 3; 3]
    # decr 3 [1;2;3];;
    - : int list option = Some [3; 1; 3]
    # decr 3 [1;1;1];;
    - : int list option = None
    

    从这个函数中枚举所有数字是微不足道的:

    let start n = Array.to_list (Array.make n n)
    
    let genblocks n =
      let rec gen = function
        | None -> []
        | Some curr -> curr :: gen (decr n curr)
      in gen (Some (start n))
    

    但重要的是,这一代的整个状态是 仅存储在一个值中,当前数字。所以你可以轻松转身 它变成了一个流:

    let genblocks n =
      let curr = ref (Some (start n)) in
      Stream.from (fun _ ->
        match !curr with
          | None -> None
          | Some block ->
            curr := (decr n block);
            Some block
      )
    
    # Stream.npeek 100 (genblocks 3);;
    - : int list list =
    [[3; 3; 3]; [2; 3; 3]; [1; 3; 3]; [3; 2; 3]; [2; 2; 3]; [1; 2; 3]; [3; 1; 3];
     [2; 1; 3]; [1; 1; 3]; [3; 3; 2]; [2; 3; 2]; [1; 3; 2]; [3; 2; 2]; [2; 2; 2];
     [1; 2; 2]; [3; 1; 2]; [2; 1; 2]; [1; 1; 2]; [3; 3; 1]; [2; 3; 1]; [1; 3; 1];
     [3; 2; 1]; [2; 2; 1]; [1; 2; 1]; [3; 1; 1]; [2; 1; 1]; [1; 1; 1]]
    

    是否存在转换生产者驱动函数的通用方法 (根据这种情况,积累物品在最自然的节奏 问题)进入消费者驱动的功能(产生元素之一) 当时,由消费者决定)?是的,我在解释它 以下博文:

    Generators, iterators, control and continuations

    最大的想法是,你可以通过机械但复杂的转变 你的代码,明确生产者的“上下文”是什么, 他现在的状态,编码为一堆复杂的价值观和 控制流(已经进行了调用,其中有条件分支 你在这一点上)。然后将此上下文转换为值 您可以使用此处使用的“数字”来推导消费者驱动 流。

答案 1 :(得分:2)

假设此代码不是家庭作业,那么您可以使用batteries中的Enum模块逐字翻译代码以逐字返回“流”。即函数Enum.pushEnum.empty。您可能已经注意到,OCaml附带的流模块非常小。最好使用替代方案。

答案 2 :(得分:0)

我遇到了类似的问题,所以我开发了一个迭代器模块(它绝对不是新的,但是我找不到能做同样事情的简单包)。

这是一个链接:https://github.com/JoanThibault/DAGaml/blob/master/tools/iter.mli

使用此模块,您可以在

中重写代码
open IterExtra (* add some handy symbols to manipulate iterators *)
let genblock n = (Iter.range 1 (4+1)) $^ n
val genblock : int -> int list Iter.iter

现在,如果您想使用某个函数prune : int list -> bool过滤它:

let genblock n = (Iter.range 1 (4+1)) $^ n |> Iter.filter prune
val genblock : int -> int list Iter.iter

最后,您可以使用一些打印函数print : int list -> unit迭代迭代器:

let n = 10;;
Iter.iter print (genblock n);;