In F #, is there a functional way to convert a flat array of elements to an array of a group of elements?

In F #, imagine that we have an array of bytes representing pixel data with three bytes per pixel in RGB order:

[| 255; 0; 0; //Solid red 0; 255; 0; //Solid green 0; 0; 255; //Solid blue 1; 72; 9; 34; 15; 155 ... |] 

It’s hard for me to understand how to functionally work with this data as it is, since one element is a really sequential block of three elements in an array.

So, I need to first group triples in an array like this:

 [| [| 255; 0; 0 |]; [| 0; 255; 0 |]; [| 0; 0; 255 |]; [| 1; 72; 9 |]; [| 34; 15; 155 |] ... |] 

Now, to assemble triples into sub-arrays, it's enough to just make a for loop, but I'm curious - is there a functional way to assemble groups of array elements in F #? My ultimate goal is not just to transform the data, as shown above, but to solve the problem in a more declarative and functional way. But I have not yet found an example of how to do this without an imperative cycle.

+3
f #
source share
5 answers

Kvb's answer may not give you what you want. Seq.windowed returns a sliding window of values, for example, [1; 2; 3; 4] [1; 2; 3; 4] [1; 2; 3; 4] becomes [[1; 2; 3]; [2; 3; 4]] [[1; 2; 3]; [2; 3; 4]] [[1; 2; 3]; [2; 3; 4]] . It looks like you want it to split into adjacent pieces. The following function takes a list and returns a list of triples ( 'T list -> ('T * 'T * 'T) list ).

 let toTriples list = let rec aux f = function | a :: b :: c :: rest -> aux (fun acc -> f ((a, b, c) :: acc)) rest | _ -> f [] aux id list 

Here's the opposite:

 let ofTriples triples = let rec aux f = function | (a, b, c) :: rest -> aux (fun acc -> f (a :: b :: c :: acc)) rest | [] -> f [] aux id triples 

EDIT

If you are dealing with huge amounts of data, here is a consistent approach with constant memory (all the created option and tuple negatively affect the GC-lower for the best version):

 let (|Next|_|) (e:IEnumerator<_>) = if e.MoveNext() then Some e.Current else None let (|Triple|_|) = function | Next a & Next b & Next c -> Some (a, b, c) //change to [|a;b;c|] if you like | _ -> None let toSeqTriples (items:seq<_>) = use e = items.GetEnumerator() let rec loop() = seq { match e with | Triple (a, b, c) -> yield a, b, c yield! loop() | _ -> () } loop() 

EDIT 2

The ebb memory usage prompted me to check, and I found that toSeqTriples is slow and causes unexpectedly frequent GCs. The next version fixes these problems and is almost 4 times faster than the list-based version.

 let toSeqTriplesFast (items:seq<_>) = use e = items.GetEnumerator() let rec loop() = seq { if e.MoveNext() then let a = e.Current if e.MoveNext() then let b = e.Current if e.MoveNext() then let c = e.Current yield (a, b, c) yield! loop() } loop() 

This is a relatively constant use of memory compared to a list or array based method, because: a) if you have seq to start with the whole sequence, you do not need to put in a list / array; and b) it also returns the sequence, making it lazy and avoiding allocating another list / array.

+4
source share

I need to first group triples in an array like this:

If you know that they will always be triples, then they represent that the tuple int * int * int more "accurate" than using an array, because it conveys the fact that there are only exactly three elements.

Other people have described various ways to massage the data, but I would recommend you not to bother (if it is not the way you described). I would choose a function to destroy your as-is array instead:

 let get i = a.[3*i], a.[3*i+1], a.[3*i+2] 

If you really want to change the view, now you can:

 let b = Array.init (a.Length/3) get 

The answer really depends on what you want to do next, though ...

+3
source share

UPDATE: As Daniel pointed out, this answer is incorrect because it creates a sliding window.

You can use the Seq.windowed function from the library. For example.

 let rgbPix = rawValues |> Seq.windowed 3 

This returns a sequence, not an array, so if you need random access, you can follow this when calling Seq.toArray .

+1
source share

Another approach that takes and gives arrays directly:

 let splitArrays n arr = match Array.length arr with | 0 -> invalidArg "arr" "array is empty" | x when x % n <> 0 -> invalidArg "arr" "array length is not evenly divisible by n" | arrLen -> let ret = arrLen / n |> Array.zeroCreate let rec loop idx = ret.[idx] <- Array.sub arr (idx * n) n match idx + 1 with | idx' when idx' <> ret.Length -> loop idx' | _ -> ret loop 0 

Or another one:

 let splitArray n arr = match Array.length arr with | 0 -> invalidArg "arr" "array is empty" | x when x % n <> 0 -> invalidArg "arr" "array length is not evenly divisible by n" | arrLen -> let rec loop idx = seq { yield Array.sub arr idx n let idx' = idx + n if idx' <> arrLen then yield! loop idx' } loop 0 |> Seq.toArray 
+1
source share

(Hat Tip: Scott Moisture ) Starting with F # 4.0 you can use Array.chunkBySize() . It does exactly what you want:

 let bs = [| 255; 0; 0; //Solid red 0; 255; 0; //Solid green 0; 0; 255; //Solid blue 1; 72; 9; 34; 15; 155 |] let grouped = bs |> Array.chunkBySize 3 // [| [|255; 0; 0|] // [| 0; 255; 0|] // [| 0; 0; 255|] // [| 1; 72; 9|] // [| 34; 15; 155|] |] 

List and Seq modules also have chunkBySize() in F # 4.0. At the time of writing, the documents on MSDN did not show chunkBySize() anywhere, but there if you are using F # 4.0.

+1
source share

All Articles