Vectorising array operations - arrays

In Excel, if we want to apply f(x)=x^2-1 to each element of a range (say A1:A10) we can write the array formula {=A1:A10^2-1}.
If I read the same range into a VBA function (which ends up being a 2D Variant), how do I do the same thing without looping?
In general, what are some (if any) FP tools available in VBA for dealing with arrays?

I think you can do something like this Range("B1").FormulaArray = A1:A10^2-1.

Related

How to use computed values inside a function that demands an array

There's a handy Excel function called SMALL that lets you find the n-th smallest value from an array. For example: SMALL({35;10;5000;6},2) = 10, the second smallest number in the set.
You could use this function by referencing an array of cells (SMALL(A1:A10,2)) or you can write an array of constant values in the formula directly (SMALL({1;2;3},2)).
Is there a way to write an array of computed values directly in the formula? It should look something like this, if using RAND to generate the values:
SMALL({RAND();RAND();RAND()},2)
but Excel doesn't allow that.
How can you use a function (like RAND) inside another function that demands an array (like SMALL)?
Yes, I'm aware that the usual solution would be to put the computed values in their own individual cells, then just use that array as the input of SMALL. It would be great if I could do this all inside a single cell.
The general answer as you may know is that you would use an array formula.
This is a bit difficult to illustrate with RAND(), but if you take a more normal function like SQRT, the usage would be
=SMALL(SQRT({9,4,1}),1)
so instead of providing a single argument to SQRT, you are providing a list of arguments which it is going to work through one at a time and return an array of 3 elements {3,2,1} which is passed to SMALL to evaluate.
If the same list of numbers was in (say) A1:A3, you would need to enter this as an array formula using CtrlShiftEnter
=SMALL(SQRT(A1:A3),1)
But as pointed out by #Jeeped, it's often more convenient to use AGGREGATE
=AGGREGATE(15,6,SQRT(A1:A3),1)
As far as I know, you can't reproduce this behaviour with RAND because it is one of the few Excel functions that takes no arguments.
If you really did want to generate an array of random numbers r where 0<=r<1 , you would have to do something like this
=SMALL((RANDBETWEEN({0,0,0},10^20-1)/10^20),1)
i.e. use RANDBETWEEN with an arbitrarily large upper limit.

Excel array countif formula

I want to use COUNTIF function to evaluate how many items out of 2,0,0,5 are greater than 2? In Countif function, first argument is range and second is criteria. I have tried the below formula. Even tried using Ctrl+Shift+Enter at the end to evaluate. But doesn't seem to work.
=COUNTIF({"2","0","0","5"},">2")
COUNTIF doesn't accept array constants (as far as I know). Try this:
=SUMPRODUCT(--({2,0,0,5}>2))
You could also create a countif-style formula like this (the combination ctrl+shift+enter):
=COUNT(IF({2,0,0,5}>2,1,""))
Recommended reading:
Array vs Range
Some functions like Offset, SumIf, CountIf, SumIfs, and CountIfs are designed to operate only on (multi-cell) range objects. Sum, SumProduct, Frequency, Linest, lookup functions, etc. take both range and array objects.
Array means: {2,0,0,5}
Range means:
To use countif, you have to use range in cells, defining the array in the formula on the go will not work.
=COUNTIF(A1:A4,">"&2)
I know this thread is a few years old, but I ended up here with a similar problem (how to use arrays, not ranges, with countif).
Although my end goal was a little different (I wanted to find items common to two arrays), I figure the workaround I came up with might be useful for others: I ended up using the "match" function coupled with "isnumber". The formula looked like this:
=isnumber(match({a},{b},0))
this will return an array of true/false which corresponds to the values in {a} that are also in {b}. In case it wasn't clear, {a} and {b} are arrays...

Redim variable number of dimensions in VBA

I have a case in an Excel macro (VBA) where I'd like to dimension an array where the number of dimensions and the bounds of each dimension are determined at runtime. I'm letting the user specify a series of combinatorial options by creating a column for each option type and filling in the possibilities below. The number of columns and the number of options is determined at run time by inspecting the sheet.
Some code needs to run through each combination (one selection from each column) and I'd like to store the results in a multidimensional array.
The number of dimensions will probably be between about 2 to 6 so I can always fall back to a bunch of if else blocks if I have to but it feels like there should be a better way.
I was thinking it would be possible to do if I could construct the Redim statement at runtime as a string and execute the string, but this doesn't seem possible.
Is there any way to dynamically Redim with a varying number of dimensions?
I'm pretty sure there is no way of doing this in a single ReDim statement. Select Case may be marginally neater than "a bunch of If...Else blocks", but you're still writing out a lot of separate ReDims.
Working with arrays in VBA where you don't know in advance how many dimensions they will have is a bit of a PITA - as well as ReDim not being very flexible, there is also no neat way of testing an array to see how many dimensions it has (you have to loop through attempts to access higher dimensions and trap errors, or hack around in the underlying memory structure - see this question). So you will need to keep track of the number of dimensions, and write long Case statements every time you need to access the array as well, since the syntax will be different.
I would suggest creating the array with the largest number of dimensions you think you'll need, then setting the number of elements in any unused dimensions to 1 - that way you always have the same syntax every time you access the array, and if you need to you can check for this using UBound(). This is the approach taken by the Excel developers themselves for the Range.Value property - which always returns a 2-dimensional array even for a 1-dimensional Range.
As I understood your users can specify dimensions and their seize by filling in the excel-sheet. This means you have to get the last row containing a value and the last column.
Therefore, have a look at: Excel VBA- Finding the last column with data
Use Redim to change the array's size. If you want to keep some kind of entries use Redim Preserve
"Some code needs to run through each combination (one selection from
each column) and I'd like to store the results in a multidimensional
array."
To begin with, I would transpose desired Range object into a Variant.
Dim vArray as Variant
'--as per your defined Sheet, range
'this creates a two dimensional array
vArray = ActiveWorkbook.Sheets("Sheet1").Range("A1:Z300").Value2
Then you could iterate through this array to possible find the size and data you need, which you may save it to an array (with the dimensions) you need.
Little Background:
Redim: Reallocates storage space for an array variable.
You are not allowed to Redim an array, if you are defining an array with a Dim statement initially. (e.g. Dim vArray(1 to 6) As Variant).
UPDATE: to show explicitly what's allowed and what's not under Redim.
Each time you use Redim it resets your original Array object to the dimensions you are defining next.
There's a way to preserve your data using Redim Preserve but that only allows you to change the last dimension of a multidimensional array, where first dimension remains as the original.

Handling of arrays in VBA macros for Excel

I'm trying to write a simulator in VB (Excel macro) where the input to a simulation is taken from cells in one sheet. The input will be placed in a number of arrays, for example timePerUser(10) and bytesPerUser(10). Then there will be some simple if/for/while stuff to make calculations based on the arrays and finally I will write the results back to Excel. SO, Excel will only be used to provide input data and to display the results, everything else is happening inside of the macro, including changing values in the arrays.
I am used working with Matlab but can't use it for this simulator, so here are my questions:
Are there any existing matrix/array operations I could use within an Excel macro? For example, is there some command to check the smallest or the next smallest value in an array? The Excel function "SMALL" would be perfect, but it doesn't seem to work in macros. Or do I simply need to solve this with for-loops?
Are there any other suggestions on how to create the arrays? Is it better to have one big matrix where each row corresponds to time, data, user etc (an NxM matrix) or is it better to have separate arrays for each parameter?
How to speed up matrix/array operations? Any general suggestions?
Thanks!
Oscar
Excel does have some simple array operations - SUMPRODUCT (Matlab equivalent: A*B'), MIN, MAX, ... Most of these, including SMALL, need to be called using Application.WorksheetFunction.xxx ; for example, the function SMALL can be called in VBA with the following:
nthSmallest = Application.WorksheetFunction.Small(r, n)
where r is an array or range, and n is an integer between 1 and r.Cells.Count
I would strongly urge you to use one variable per conceptual variable, rather than trying to be clever with multiple entities in one 2D array. Speedwise I suspect the difference is small; but the opportunity to write unreadable code is significant.
As for speeding things up: it really helps to define your variables (and especially your arrays) as some fixed type (rather than Variant), e.g.:
Dim a(1 To 10) as Double
Dim ii as Integer
and it may help a little bit to have all of the arrays use the same (default) base. Since I'm a Matlab junkie myself, I often work with array indices starting at 1 - you enforce this in VBA by adding
Option Base 1
at the start of your module. At this point, if you declare
Dim a(10) as Double
it is equivalent to
Dim a(1 To 10) as Double
Is also good practice for someone who is not proficient in VBA, to add
Option Explicit
at the start of the module, as it will ensure that any variables you did not declare (or more often, that you misspelled) will generate an interpreter error.
One other thing about arrays: as a Matlab person you are used to Matlab increasing array sizes as needed. In VBA it is possible to change array size dynamically at runtime, using, for example
ReDim Preserve a(1 To 20)
which will make the array a 20 elements in size. If it was shorter, it will be padded with zeros; if it was longer, it will be trimmed. This CAN be useful, but is QUITE expensive, since often the entire array needs to be copied to a new location. When you don't know in advance how large your array will be, it's better to overestimate (and check bounds) than to ReDim every time it needs to get bigger - and do a final ReDim at the end to get it to the right size.
Hope this is enough to get your started!

Skip column in an array

I have a VBA function that returns an array to be displayed in Excel. The array's first two columns contain ID's that don't need to be displayed.
Is there any way to modify the Excel formula to skip the first two columns, without going back to create a VBA helper to strip off the columns?
The formula looks like this, where the brackets let the array be displayed across a span of cells:
{=GetCustomers($a$1)}
The closest thing Excel has to built-in array manipulation is the 'INDEX' function. In your case, if the array returned by your 'GetCustomers' routine is a single row, and if you know how long it is (which I guess you do since you're putting it into the sheet), you can get what you want by doing something like this:
=INDEX(GetCustomers($A$1),{3,4,5})
So say GetCustomers() returned the array {1,2,"a","b","c"}, the above would just give back {"a","b","c"}.
There are various ways to save yourself having to type out your array of indices, though. For example,
=COLUMN(C1:E1)
will return {3,4,5}, and you can use that instead:
=INDEX(GetCustomers($A$1),COLUMN(C1:E1))
This trick doesn't work with a true 2-D array, though. 'INDEX' will return a whole row or column if you pass in a zero in the right place, but I don't know how to make it return a 2-D subset. EDIT: You can do it, but it's cumbersome. Say your array is 2x5, and you want the last three columns. You could use:
=INDEX(GetCustomers($A$1), {1,1,1;2,2,2}, {3,4,5;3,4,5})
(FURTHER EDIT: chris neilsen provides a nice way to compute those arrays in his answer.)
Charles Williams has a link on his website that explains more about using arrays like this here:
http://www.decisionmodels.com/optspeedj.htm
He posted that in response to this question I asked:
Is there any documentation of the behavior of built-in Excel functions called with array arguments?
Personally, I use VBA helper functions for things like this. With the right library routines, you can do something like:
=subseq(GetCustomers($A$1),3,3)
in the 1-D case, or:
=hstack(subseq(asCols(GetCustomers($A$1)),3,3))
in the 2-D case, and it's much more clear.
simplest solution is to just hide the first two columns
another may be to use OFFSET to resize the returned array
syntax is
OFFSET(reference,rows,cols,height,width)
I suggest modifying the 'GetCustomers' function to include an optional Boolean variable to tell the function to return all the columns, or just the single column. That would be the cleanest solution, instead of trying to handle it on the formula side.
Public Function GetCustomers(rng as Range, Optional stripColumns as Boolean = False) as Variant()
If stripColumns Then 'Resize array to meet your needs
Else 'return full sized array
End If
End Function
You can use the INDEX function to extract items from the return array
- formula is in an range starting at cell B2
{=INDEX(getcustomers($A$1),ROW()-ROW($B$2)+1,COLUMN()-COLUMN($B$2)+3)}

Resources