Reputation: 1784
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
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
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
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