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

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

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

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

谢谢。

===============>>#1 票数:3 已采纳

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

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

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

  3. 指向将任何生产者转变为按需流的通用技术的链接

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

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任何函数A -> B使用类型为C的可变类型转换为类型为A * C -> B * C的函数A * C -> B * C接收状态,并返回其修改后的值 - 这就是所谓的“状态monad”。 所以我将简单地向你的函数loopiter添加一个额外的参数blocks ,并使它返回不是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和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]]

有没有一种通用的方法来转换生产者驱动的函数(根据问题在最节制的情况下累积项目)到消费者驱动的函数(当消费者决定时,产生一个元素)? 是的,我在以下博客文章中解释它:

生成器,迭代器,控制和延续

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

===============>>#2 票数:2

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

===============>>#3 票数: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过滤它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迭代迭代器print : int list -> unit

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

  ask by user2211937 translate from so

未解决问题?本站智能推荐: