Stream 将ocaml函数转换为流

Stream 将ocaml函数转换为流,stream,ocaml,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

假设我有以下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;2];…;[4;4;4]

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

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

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


谢谢。

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

我的答案中有三个不同的方面:

  • 一种通用技术的演示,用于消除可变变量

  • 一种算法特定的技术,使得生成流非常容易

  • 指向通用技术的链接,可将任何制作者转变为按需流

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

    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
    接收状态并返回其修改后的值-- 这就是所谓的“状态单子”。因此,我将简单地添加一个 附加参数
    阻止功能
    循环
    iter
    ,以及 使其返回的不是
    单元
    ,而是
    整数列表

    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和3的所有3个组合 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]]
    
    是否有转换生产者驱动函数的通用方法 (以最自然的节奏积累物品,根据 问题)转化为消费者驱动的功能(即产生一个元素 当时,何时由消费者决定)?是的,我用英语解释 以下博文:

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

    我有一个与您类似的问题,所以我开发了一个迭代器模块(它肯定不是新的,但我无法找到一个简单的包来做同样的事情)

    以下是一个链接:

    使用此模块,您可以在中重新编写代码

    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);;
    

    除非我读错了,
    Enum.push
    将允许我构造流,但我仍然必须先构造整个流,然后才能清空它(也就是说,我相信使用
    Enum.push
    与在代码的最后一行中使用
    stream.of_list!blocks
    )具有相同的效果。