excelexcel-formula

Tiling an Excel array in 2 dimensions, ideally not using VBA


I'm looking to tile an Excel array in 2 dimensions, as defined by another array, ideally not using VBA.

To explain better, what I would like is an excel formula for f(A, B)

Where A is boolean and =

[[1, 0],
 [0, 1]

Where B =

[[1, 2],
 [3, 4]

And f(A, B) tiles B according to bool values in A, in this case, f(A, B) =

[[1, 2, 0, 0],
 [3, 4, 0, 0],
 [0, 0, 1, 2],
 [0, 0, 3, 4]]

My initial thoughts were of =IF(MUNIT(2), {1, 2;3, 4}, {0, 0;0, 0}) but the output does not expand well.


Solution

  • This is my effort so far - hopefully it's fairly general:

    =LET(A,A1:B2,B,D1:E2,DROP(REDUCE(SEQUENCE(1,COLUMNS(A)*COLUMNS(B)),SEQUENCE(ROWS(A)),LAMBDA(rowAcc,row,VSTACK(rowAcc,
       DROP(REDUCE(SEQUENCE(ROWS(B)),SEQUENCE(1,COLUMNS(A)),LAMBDA(colAcc,col,HSTACK(colAcc,B*INDEX(A,row,col)))),,1)))),1))
    

    enter image description here


    You could also use Makearray:

    =LET(A,A1:B2,B,D1:E2,rA,ROWS(A),rB,ROWS(B),cA,COLUMNS(A),cB,COLUMNS(B),
    MAKEARRAY(rA*rB,cA*cB,LAMBDA(r,c,INDEX(A,(r-1)/rB+1,(c-1)/cB+1)*INDEX(B,MOD(r-1,rB)+1,MOD(c-1,cB)+1))))
    

    (Same logic as @Scott Craner's legacy version)


    Here is some sample VBA code in case you wanted to go down that route:

    Sub Kron()
        Dim AR As Range, BR As Range, OutR As Range
        
        ' A is the boolean array, B is array to be tiled.
        
        Dim A() As Variant
        Dim B() As Variant
        
        Dim aRows As Long, bRows As Long, aCols As Long, bCols As Long, outRows As Long, OutCols As Long
        
        Dim aRow As Long, aCol As Long, bRow As Long, bCol As Long, outRow As Long, outCol As Long
        
        Dim Out() As Integer
        
        Set AR = Range("A1:C2")
        Set BR = Range("D1:F3")
        Set OutR = Range("Q1")
        
        A = AR.Value
        B = BR.Value
        
        aRows = UBound(A, 1)
        aCols = UBound(A, 2)
        
        bRows = UBound(B, 1)
        bCols = UBound(B, 2)
        
        
        outRows = aRows * bRows
        OutCols = aCols * bCols
        
        ReDim Out(outRows, OutCols)
        
        ' Loop over boolean array A
        
        outRow = 1
        For aRow = 1 To aRows
            outCol = 1
            For aCol = 1 To aCols
            
                ' Only call Tile if A contains 1
                
                If A(aRow, aCol) = 1 Then Call Tile(Out, B, outRow, outCol, bRows, bCols)
                
                outCol = outCol + bCols
            Next aCol
            outRow = outRow + bRows
        Next aRow
              
        OutR.Resize(outRows, OutCols) = Out
         
    End Sub
    
    Private Sub Tile(Out, T, startRow, startCol, height, width)
        Dim tileRow As Long
        Dim tileCol As Long
        Dim outRow As Long
        Dim outCol As Long
        
        'Loop over tiling array locally called T
        
        outRow = startRow
        For tileRow = 1 To height
            outCol = startCol
            For tileCol = 1 To width
                Out(outRow, outCol) = T(tileRow, tileCol)
                outCol = outCol + 1
            Next tileCol
            outRow = outRow + 1
        Next tileRow
    End Sub