nik
nik

Reputation: 1784

Concat 2D Arrays in f#

How can I combine two 2d arrays generally?

My assumption (I could obviously easily test this) is that they always have the same number of columns:

let concatArrays (arr1:obj[,]) (arr2:obj[,]) = 
    ([arr1; arr2]) |> Array2d.concat

This function doesnt exist though. Just to be clear, the result should produce a 2d array with length = sum of lengths and same number of columns as the original arrays2D and should be the same type as the input, here obj[,]. I could obviously do this in a looping construct but I was wondering about an f# way. Thanks.

I tried this:

let append2D (arr1:float[,]) (arr2:float[,]) = 
    let cls = arr1.GetLength 1
    let rows1 = arr1.GetLength 0
    let rows2 = arr2.GetLength 0
    Array2D.init (rows1+rows2) cls (fun i j -> match i with | a when a <= rows1 -> arr1.[i,j] | _ ->  arr2.[i,j])

But this comes back with index out of bounds error.

Update of last row:

 Array2D.init (rows1+rows2) cls (fun i j -> if i < rows1 then arr1.[i,j] else arr2.[i,j])  

Update working solution:

 Array2D.init (rows1+rows2) cls (fun i j -> if i < rows1 then arr1.[i,j] else arr2.[i-rows1,j])  

thanks all

Upvotes: 5

Views: 1318

Answers (3)

Darien Shannon
Darien Shannon

Reputation: 217

@Rustam, Thanks for this. I was in need of these functions for working with excel ranges. After using these functions I found a few places for improvement.

First, rather than assuming that a1 and a2 have zero-based indexes, I recommend using the Array2D.base1 and Array2D.base2 for your indexes in the Array2D.blit functions. Note, it took me about 4 hours of pulling my hair out to figure out that this is what was causing some issues in my code.

i.e. for the joinByRows function:

Array2D.blit a1 (Array2D.base1 a1) (Array2D.base2 a1) result 0 0 a1l1 a1l2
Array2D.blit a2 (Array2D.base1 a2) (Array2D.base2 a2) result a1l1 0 a2l1 a2l2

Second, your joinMany function can be simplified significantly by using Seq.fold :

let joinMany joiner (a: seq<'a[,]>)  = 
        Seq.fold joiner (Seq.head a) (Seq.tail a)

I didnt check performance but I would imagine that the built in function would be more optimized.

Upvotes: 0

Rustam
Rustam

Reputation: 1776

@Gene has provided an excellent solution, using built-in blit function seems to be very useful in here.

I would like to post my usage of his function as an extension to the modules Array and Array2D for those who might find it useful:

 module Array = 
    let first (arr: 'a array) = arr.[0]
    let others (arr: 'a array) = arr.[1..]
    let split arr = first arr, others arr  

module Array2D = 
    let joinByRows (a1: 'a[,]) (a2: 'a[,]) =
        let a1l1,a1l2,a2l1,a2l2 = (Array2D.length1 a1),(Array2D.length2 a1),(Array2D.length1 a2),(Array2D.length2 a2)
        if a1l2 <> a2l2 then failwith "arrays have different column sizes"
        let result = Array2D.zeroCreate (a1l1 + a2l1) a1l2
        Array2D.blit a1 0 0 result 0 0 a1l1 a1l2
        Array2D.blit a2 0 0 result a1l1 0 a2l1 a2l2
        result

    let joinByCols (a1: 'a[,]) (a2: 'a[,]) =
        let a1l1,a1l2,a2l1,a2l2 = (Array2D.length1 a1),(Array2D.length2 a1),(Array2D.length1 a2),(Array2D.length2 a2)
        if a1l1 <> a2l1 then failwith "arrays have different row sizes"
        let result = Array2D.zeroCreate a1l1 (a1l2 + a2l2)
        Array2D.blit a1 0 0 result 0 0 a1l1 a1l2
        Array2D.blit a2 0 0 result 0 a1l2 a2l1 a2l2
        result

    // here joiner function must be Array2D.joinByRows or Array2D.joinByCols
    let joinMany joiner (a: seq<'a[,]>)  = 
        let arrays = a |> Array.ofSeq
        if Array.length arrays = 0 then 
            failwith "no arrays"
        elif Array.length arrays = 1 then 
            Array.first arrays
        else
            let rec doJoin acc arrays = 
                if Array.length arrays = 0 then
                    acc
                elif Array.length arrays = 1 then
                    joiner acc (Array.first arrays)
                else
                    let acc = joiner acc (Array.first arrays)
                    doJoin acc (Array.others arrays)
            doJoin <|| Array.split arrays
            // or doJoin arrays.[0] arrays.[1..] 

Upvotes: 1

Gene Belitski
Gene Belitski

Reputation: 10350

Following this recommendation here is a concat function for two equal column size Array2D arguments of any type 'a:

let concat (a1: 'a[,]) (a2: 'a[,]) =
    let a1l1,a1l2,a2l1,a2l2 = (Array2D.length1 a1),(Array2D.length2 a1),(Array2D.length1 a2),(Array2D.length2 a2)
    if a1l2 <> a2l2 then failwith "arrays have different column sizes"
    let result = Array2D.zeroCreate (a1l1 + a2l1) a1l2
    Array2D.blit a1 0 0 result 0 0 a1l1 a1l2
    Array2D.blit a2 0 0 result a1l1 0 a2l1 a2l2
    result

You may check this experimentally, but it would have times better performance, than any variant based on Array2D.init because Array2D.zeroCreate and Array2D.blit implementations are highly optimized.

Upvotes: 7

Related Questions