Convert ocaml function to stream

Say I have the following OCaml code code:

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 

This creates a list of lists: [[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]] .

Now I want to convert this to a stream (using the Stream module or something similar). However, I do not understand how to do this, while preserving the overall recursive structure of the current code. The reason I would like to maintain this structure is because it allows me to easily cut out lists containing certain properties during generation. For example, with this code structure during generation, I can easily cut out, say, lists containing a sublist [1;1] . (The above is an example of a game, and my actual application is more complicated ...).

Any hints / ideas / pointers on how to convert the above code snippet into a "streaming" instance? I was kind of stuck in the inability to “retreat” after the depth of zero hit ...

edit: Another way to look at the problem: is there a way to convert genblocks to get rid of the list link? This is similar to the first step needed to ensure thread compatibility.

Thanks.

+4
source share
3 answers

There are three different things in my answer:

  • demonstration of a general method to get rid of a mutable variable

  • algorithm-specific method that makes it very easy to create a stream

  • reference to the general method of turning any manufacturer into a stream on demand

First, make your algorithm general at the heart of the enumeration:

 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 

Despite what the code is doing at the moment, it’s a very simple way to get rid of the enumeration: turn any function of type A -> B that uses a mutable type with type C into a function of type A * C -> B * C , which gets the state and returns its changed value - this is what is called the "state monad." Therefore, I simply add an additional blocks parameter to your loop and iter functions, and make it not return unit , but 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 [] [] 

Now let's see what exactly this algorithm does:

 # 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]] 

When called with argument 3 (hardcoded in code 4), these algorithms return all 3-combinations of numbers 1, 2, and 3. Otherwise, it lists all three-digit numbers in the numbering system in base 3 (using numbers from 1 to 3 instead 0 and 2, as usual).

There is a very simple way to list the numbers that you learned in school: move from number to next, just increase (or decrease). In your case, the list starts with a "large" number and goes to a "small" number, so we are going to decrease. With the fact that your base [1; N], not [0; N-1], decrement function written

 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 

I made it return No when we reach 0 (on your system, [1; 1; 1 ..]) to easily stop the listing at this point.

 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 

It is trivial to list all the numbers from this function:

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

But it is important that the entire state of the generation is stored in only one value, the current number. This way you can easily turn it into a stream:

 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]] 

Is there a general way to convert manufacturer-driven functions (which accumulate objects in a rhythm that are most natural according to the problem) to consumer-oriented functions (which produce elements one at a time when it is decided by the consumer)? Yes, and I will explain this in the following blog post:

Generators, Iterators, Control and Continuation

The big idea is that you can, thanks to the mechanical, but complex transformations of your code, indicate what the “context” of the producer is, what its current state is, as encoded as a complex mess of values ​​and (which were called in which the conditional branch you are on this moment). This context then turns into a value that you can use as the “numbers” used here to produce a consumer-oriented flow.

+3
source

Assuming this code is not homework, you can pretty much translate your code to return the "stream" verbatim using the Enum module in batteries . Namely, the functions Enum.push and Enum.empty . The stream module that ships with OCaml is extremely minimal, as you probably noticed. Better to use an alternative.

+2
source

I had a similar problem for you, so I developed an iterator module (this is definitely not new, but I could not find a simple package that did the same)

Here is the link: https://github.com/JoanThibault/DAGaml/blob/master/tools/iter.mli

With this module you can rewrite the code in

 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 

now if you want to filter it with some prune : int list -> bool function prune : int list -> bool :

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

Finally, you can iterate over the iterator using some print function print : int list -> unit :

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

All Articles