Slicing Arrays with VBA

A recent comment asked about the most efficient way to write part of an array to the spreadsheet from VBA. This post looks at three alternative methods which have advantages in different circumstances (thanks to Lori Miller for reminding me of the Index alternative, which I have written about previously, then forgotten about).

Download all the functions discussed in this post from VBIndex.xlsb.

If the source array is in the form of a range object the task can be done very simply with a combination of the Offset and Resize methods:

Function SliceArrayO(DatRange As Variant, TLR As Long, TLC As Long, NumR As Long, NumC As Long) As Variant

    ' Slice an array passed as a range with the Offset method
    ' Ranges are not converted to arrays
    ' No step size argument

    If NumR = 0 Then NumR = DatRange.Rows.Count - TLR + 1
    If NumC = 0 Then NumC = DatRange.Columns.Count - TLC + 1

    SliceArrayO = DatRange.Offset(TLR, TLC).Resize(NumR, NumC)
End Function

An alternative is the worksheet Index function:

Function SliceArrayI(DatRange As Variant, Optional TLR As Long = 1, Optional TLC As Long = 1, _
    Optional NumR As Long = 0, Optional NumC As Long = 0, _
    Optional RowStep As Long = 1, Optional ColStep As Long = 1) As Variant

    Dim i As Long, j As Long, RowA() As Variant, ColA() As Variant
    ' Slice an array passed as a range using the Index function
    ' Optional step size argument

    If NumR = 0 Then
        NumR = DatRange.Rows.Count - TLR + 1
        If RowStep > 1 Then NumR = Int((NumR - 1) / RowStep) + 1
    End If
    If NumC = 0 Then
        NumC = DatRange.Columns.Count - TLC + 1
        If ColStep > 1 Then NumC = Int((NumC - 1) / ColStep) + 1
    End If
    ReDim RowA(1 To NumR)
    ReDim ColA(1 To NumC)

    If RowStep > 1 Then NumR = 1 + (NumR - 1) * RowStep
    If ColStep > 1 Then NumC = 1 + (NumC - 1) * ColStep

    j = 0
    For i = 1 To NumR Step RowStep
        j = j + 1
        RowA(j) = Array(TLR + i - 1)
    Next i

    j = 0
    For i = 1 To NumC Step ColStep
        j = j + 1
        ColA(j) = TLC + i - 1
    Next i

    SliceArrayI = Application.Index(DatRange, RowA, ColA)
End Function

Note that the array defining the rows in the output array (RowA) must be an array of arrays.
This procedure has the advantage that it is possible to specify a step for the output rows and columns, so that for instance every other row may be output. Also the input array may be either a range or a variant array.

A third alternative is to simply loop through the input array, and copy selected values to a new array:

Function SliceArrayV(DatRange As Variant, Optional TLR As Long = 1, Optional TLC As Long = 1, _
    Optional NumR As Long = 0, Optional NumC As Long = 0, _
    Optional RowStep As Long = 1, Optional ColStep As Long = 1) As Variant

    Dim i As Long, j As Long, m As Long, n As Long, NewA() As Variant
    ' Slice an array passed as a range or variant array with a double loop
    ' Ranges are not converted to arrays
    ' Optional step size argument

    If TypeName(DatRange) = "Range" Then
        If NumR = 0 Then
            NumR = DatRange.Rows.Count - TLR + 1
            If RowStep > 1 Then NumR = Int((NumR - 1) / RowStep) + 1
        End If
        If NumC = 0 Then
            NumC = DatRange.Columns.Count - TLC + 1
            If ColStep > 1 Then NumC = Int((NumC - 1) / ColStep) + 1
        End If
    Else
        If NumR = 0 Then
            NumR = UBound(DatRange) - TLR + 1
            If RowStep > 1 Then NumR = Int((NumR - 1) / RowStep) + 1
        End If
        If NumC = 0 Then
            NumC = UBound(DatRange, 2) - TLC + 1
            If ColStep > 1 Then NumC = Int((NumC - 1) / ColStep) + 1
        End If
    End If

    ReDim NewA(1 To NumR, 1 To NumC)

    If RowStep > 1 Then NumR = 1 + (NumR - 1) * RowStep
    If ColStep > 1 Then NumC = 1 + (NumC - 1) * ColStep

    m = 0
    For i = 1 To NumR Step RowStep
        m = m + 1
        n = 0
        For j = 1 To NumC Step ColStep
            n = n + 1
            NewA(m, n) = DatRange(TLR + i - 1, TLC + j - 1)
        Next j
    Next i

    SliceArrayV = NewA
End Function

This code will also work on an array passed as either a Range object or a variant array, and allows a step size to be specified for the output array. As a possibly faster variation, the alternative code below converts an input range to a variant array before extracting the data. The option for step sizes has been removed from this version, to keep the code as simple as possible:

Function SliceArrayV2(DatRange As Variant, Optional TLR As Long = 1, Optional TLC As Long = 1, _
    Optional NumR As Long = 0, Optional NumC As Long = 0) As Variant

    Dim i As Long, j As Long, NewA() As Variant
    ' Slice an array passed as a range or variant array with a double loop
    ' Ranges are converted to arrays
    ' No step size argument

    If TypeName(DatRange) = "Range" Then DatRange = DatRange.Value2
    If NumR = 0 Then NumR = UBound(DatRange) - TLR + 1
    If NumC = 0 Then NumC = UBound(DatRange, 2) - TLC + 1

    ReDim NewA(1 To NumR, 1 To NumC)

    For i = 1 To NumR
        For j = 1 To NumC
            NewA(i, j) = DatRange(TLR + i - 1, TLC + j - 1)
        Next j
    Next i

    SliceArrayV2 = NewA
End Function

Finally, I have written a “front-end” function that selects the most appropriate function to use, of the four available, based on some benchmark tests described later.  The SliceA function works as follows:

  • If it is passed a variant array (rather than a range object) it calls SliceArrayV2 if there is no step size specified for the output, or SliceArrayV for row or column steps (or both) greater than 1.
  • If it is passed a range object it calls SliceArrayO if there are no steps in the output, or SliceArrayI if there are.
Function SliceA(DatRange As Variant, Optional TLR As Long = 1, Optional TLC As Long = 1, _
    Optional NumR As Long = 0, Optional NumC As Long = 0, _
    Optional RowStep As Long = 1, Optional ColStep As Long = 1) As Variant
    Dim i As Long, j As Long, RowA() As Variant, ColA() As Variant
    ' Call one of four Slice functions, depending on input and output types

    If RowStep = 0 Then RowStep = 1
    If ColStep = 0 Then ColStep = 1

    If TypeName(DatRange) <> "Range" Then
        If RowStep = 1 And ColStep = 1 Then
            SliceA = SliceArrayV2(DatRange, TLR, TLC, NumR, NumC)
        Else
            SliceA = SliceArrayV(DatRange, TLR, TLC, NumR, NumC, RowStep, ColStep)
        End If
    ElseIf RowStep = 1 And ColStep = 1 Then
        SliceA = SliceArrayO(DatRange, TLR - 1, TLC - 1, NumR, NumC)
    Else
        SliceA = SliceArrayI(DatRange, TLR, TLC, NumR, NumC, RowStep, ColStep)
    End If
End Function

Examples of the use of the SliceA function as a User Defined Function (UDF) are shown in the screen shots below.  In each case the data is extracted from a range with 8 columns and 262 rows:

SliceA1
SliceA2
SliceA3

Benchmark results for the slicing functions under different conditions are shown in the tables below.  For each case there are 11 different results:

  • 1: SliceArrayI (using the index function) passed a range object.
  • 2-4: SliceArrayV (using a double loop) 1) passed a variant array, 2) range converted to array, then passed to the function, 3) passed a range object.
  • 5-7: SliceArrayV2 (modified double loop) with the same three cases as above.
  • 8: SliceArrayO (using the Offset method) passed a range object.
  • 9-11: SliceA with the same 3 cases as SliceArrayV and SliceArrayV2

The first case was 1000 iterations extracting a 5×5 array from a 10×10 array:
SliceA4

For a small array the functions looping through an array were significantly faster than those that operated on a range, but when passed a range these functions were significantly slower.  Of the two functions operating on a range, SliceArrayO was significantly faster than SliceArrayI.  The SliceA function was only marginally slower than the best case for each of the three variations.

The second case was 100 iterations extracting a 5×5 array from a medium sized array (1000 x 10)
SliceA5

These results are similar to the first case, except that SliceArrayV2 passed a range is now three orders of magnitude slower than when passed a variant array.

The third case was 10 iterations extracting a 5×5 array from a large array (5000 x 1000)
SliceA6

Operating on a large range the functions that convert the range to an array have become much slower than the others.  Note that even though this operation is only repeated 10 times, V2-Range is more than 100 times slower than V2-Array 2, which performs the range to array operation only once.

The next case extracted a 500×500 array from the same large array as before (5000×1000)
SliceA7

Relative times are in the same order, but the relative differences are much smaller.

Finally a small array was extracted from near the end of the large array, again repeated 10 times.
SliceA8

For this case the functions operating on range objects were significantly slower, whereas those operating on arrays were as fast or faster.  Those functions requiring the conversion of a range object to a variant array were again very slow, especially when this operation was repeated 10 times, resulting in an execution time about 100 times slower than the function otherwise following the same operations, and almost 500,000 times slower than the fastest function.

In summary:

  • When operating on arrays, the looping functions were significantly faster on small arrays, and had similar performance on very large arrays.
  • When operating on range objects the SliceArrayO function (using the offset method) was significantly faster than using the Index function.  Both were significantly faster than looping on a range method, and hugely faster than converting the range to a variant array, especially for large arrays.
  • The SliceA function performed close to the best timing, except when conversion of a large range to an array (outside the function) was included in the timing.
Posted in Arrays, Excel, UDFs, VBA | Tagged , , , , , | 7 Comments

“Davy Graham has a theory” …

… is not an official Googlewhack because it has five words and is enclosed in quotes; nonetheless this exact search in Google returns just one hit (at least it did until today):

“She Moved Thru’ the Bizarre”: Davy Graham and Irish Orientalism

See the previous post for the source of the quote.

Posted in Bach | Tagged , , | Leave a comment

West meets East

On Friday I attended an excellent concert by the Australian Chamber Orchestra and the Tawadros brothers, Joseph and James. They played a mixture of Verdi’s 4 Seasons interspersed by original pieces by Joseph Tawadros. The Youtube clip froman earlier concert below gives a feeling for the exceptional musicianship of these people.

The playing of Joseph Tawadros reminded me of the guitar of Davey Graham, who some 50 years ago moved in the opposite direction.  The clip below shows a live performance of She Moves Through the Fair, otherwise known as She Moved Through the Bizarre/Blue Raga (edit 24 May 2015 – the original video has gone private, now replaced with an audio track, probably of the same performance):

For more on Davy Graham (including two full audio recordings) see: She Moves Through the Fair: Davy Graham (and Jimmy Page) Just found, a preview for the ACO/Tawadros concert:

Posted in Bach | Tagged , , , , | 1 Comment

Beams may kiss, but do not hug …

… not full length anyway.

This post was inspired by a now very long discussion at Eng-Tips on the apparently simple question of what happens when you place one short beam on top of a longer one, and apply a load to the top beam.  How do the beams deflect, and how is the load shared between the beams, or does the top beam make no difference, since all of the load must go through the lower beam, before it reaches the support?

To keep things (relatively) simple, I will consider here a 10 metre long beam with a shorter beam placed symmetrically about the centre line, with a uniform distributed load over the full length of the upper beam.  If the upper beam is sufficiently stiff it will simply span across the centre of the lower beam, transferring the applied loads as point loads at its ends.  More interesting is what happens if the top beam stiffness is reduced so that it touches the lower beam in the middle, as well as both ends.

I have analysed this situation using the SSSPanU function from the ConBeamU spreadsheet.  A modified version of ConBeamU including the analysis described here can be downloaded from ConBeamU-2Beams.zip.

The procedure used in the analysis is:

Assuming two beams, the top one shorter than the bottom, arranged symmetrically about mid-span, with the top beam subject to a UDL of its full length.

  • Check if the beams contact at mid-span. If they do not, or if it is a point contact, find the moments in both beams from statics. If they do contact over a finite length:
  • Find the total moment at mid-span from statics
  • Distribute this moment in proportion to the beams EI values.
  • Assume some contact length, symmetrical about mid-span.
  • Starting from the mid-span moments, find the moments in both beams at the start of the contact length
  • Calculate the point force transfer at the start of the upper beam required for static equilibrium with the calculated moments.
  • Find the point force transfer at the start of the contact length required for overall static equilibrium.
  • Apply these loads to both beams assuming point contact of the ends of the upper beam on the lower beam.
  • Calculate the difference in deflection at mid span, relative to the ends of the upper beam.
  • Adjust the contact length until the difference in deflection is zero.

The spreadsheet allows this procedure to be carried out quickly, using the Excel Solver.

The beam lengths, EI of the lower beam, and relative stiffness factor of the two beams (EI Lower / EI Upper) are entered:

Stacked Beams2-0-1
The spreadsheet calculates the point load transfer at the ends of the upper beam, and the ends of the contact zone, specified in cell L2:
Stacked Beams2-0-2
These values are transferred to the input data for two copies of the SSSPanU  function:
Stacked Beams2-0-3
The functions calculate the moments, shears, slope and deflection along both beams.  The Excel solver can be used to automatically adjust the start of the contact zone dimension so that the gap between the two beams at mid span is zero:
Stacked Beams2-0-4
Typical results are shown below, plotted from the left support of the lower beam to mid-span.  The green line in the left hand graph is the gap between the two beams (right hand scale).  Note that the gap is zero over the contact zone, increases to a maximum, then returns to zero for a point contact at the end of the upper beam.  In the moment diagram the upper beam moments (red line) are factored by the beam stiffness ratio.  Note that the factored moments of the two beams are exactly equal over the contact zone, because the curvatures of the two beams are equal over this length.
Stacked Beams2-1
Reducing the stiffness of the top beam increases the contact length, but the moments and gaps follow the same form.
Stacked Beams2-2
Even reducing the top beam EI to 1/500 that of the lower beam, the contact length does not extend to the top of the upper beam.
Stacked Beams2-3
These graphs show a comparison of the spreadsheet results with a finite element analysis using Strand7, with the beams connected by short contact elements with zero friction and zero tensile strength.  The FEA results agree very closely with the spreadsheet analysis.
Stacked Beams2-4

Posted in Beam Bending, Excel, Finite Element Analysis, Frame Analysis, Newton, UDFs, VBA | Tagged , , , , , , , | Leave a comment

Links on Lists, and other things.

Daily Dose of Excel recently had a post on interacting with List Objects from VBA.  Since I make very little use of Lists (or Tables as they are now known in Excel, but not VBA), I thought I’d have a search for more information.  There doesn’t seem to be a huge amount of information available, but a good starting point would be:

Working with Tables in Excel 2013, 2010 and 2007 (VBA) from Jan-Karel Pieterse.

See also the first comment below from Winston Snyder, with 6 more List related links.

Other useful links gleaned from DDoE:

EXCELXOR is a relatively new Excel blog with excellent content:

If you’re looking for articles relating to advanced, formula-based Excel solutions then you’ve come to the right place.

Please visit my blog to see a whole host of innovative techniques developed using worksheet formulas alone. These techniques – accompanied by detailed and instructive explanations of their workings – are not only of theoretical interest to the reader wishing to explore the possibilities of what can be achieved in this field, but also offer practical solutions to a plethora of everyday issues which the average Excel user may encounter.

Excellerando has been going for some time.  Not a huge turnover, but over the years it has built up an excellent library of VBA code advice and examples.

Finally another Alfred Vachris link:

VBA Projects by Mark Kubiszyn

Welcome to my blog. This is all about imparting knowledge. I want to share some of the insight and learnings from six years of developing in VBA for Excel. I also want to cover off some innovative techniques, coding best practices and share some of my development Tools & Reports.

Please feel free to download and share my work – you can use the Code and techniques to develop your own VBA Projects. To send me a message, you can contact me on my Homepage

Posted in Computing - general, Excel, VBA | Tagged , , , , | 2 Comments