Can I have an "If" statement in a function parameter? - arrays

I'd like to use an IF statement in my VBA code where I'm calling up a Function and passing to it two parameters. The reason I need the IF statement is because the parameter is an array object that I pass to the function. Here's an abbreviation of the code that contains the problem:
For i = 1 To UBound(FilterArray)
For j = LBound(ptsheets, 1) To UBound(ptsheets, 1)
Filter_PivotField_Master _
pvtField:=ThisWorkbook.Worksheets(ptsheets(j, 1)).PivotTables(ptsheets(j, 2)).PivotFields(FilterArray(i, 1)), _
FilterTypeArray:= If FilterArray(i, 2) = 1 Then
InvCodeArray
Else
BoardMonthArray
End If
Next j
Next i
As you can see, I'm looping through the ptsheets array and for each entry, I'm calling up the Filter_PivotField_Master Function. The function requires two inputs (a pivotfield and a string array for the filtering). The array called "FilterArray" simply contains the info needed to pass along to the function. Because I can't seem to store the string array (either InvCodeArray or BoardMonthArray) within the "FilterArray," I wanted to use this IF statement to choose between either array based on if FilterArray(i,2) is equal to "1" or not. Is an "IF" statement option possible here?

Don't do that. Extract a method/procedure out of the inner loop, and parameterize it instead.
For j = LBound(ptsheets, 1) To UBound(ptsheets, 1)
Filter_PivotField_Master _
pvtField:=ThisWorkbook.Worksheets(ptsheets(j, 1)).PivotTables(ptsheets(j, 2)).PivotFields(thePivotField), _
FilterTypeArray:= theFilterType
Next j
Then have the calling code (the outer loop) implement the conditional logic that determines what parameters to give it.
Dim thePivotField
Dim theFilterType
For i = 1 To UBound(FilterArray)
thePivotField = FilterArray(i, 1)
If FilterArray(i, 2) = 1 Then
theFiltertype = InvCodeArray
Else
theFilterType = BoardMonthArray
End If
TheExtractedMethod i, thePivotField, theFilterType
Next i
Your code will be much easier to follow - and to debug.
On a related note, I made a few assumptions just to get your snippet to compile with Option Explicit on (and an IIf instead of the illegal If...Else block), and Rubberduck's last release (v1.4.3) correctly extracted the selected inner loop into its own method:
There are a few known bugs with Rubberduck 1.4.3, and v2.0 is coming soon-ish, but I thought this might interest you. I don't know any other refactoring tools for VBA. Note that Option Explicit is pretty much a hard requirement for this to work, since Rubberduck works off declarations and can't resolve undeclared variables.
Disclaimer: I wrote this refactoring tool.

Use an immediate IF (IIF) for this purpose.
FilterTypeArray = IIF(FilterArray(i, 2) = 1, InvCodeArray, BoardMonthArray)

Related

accessing boundaries of array without duplicating lots of code

When I try to compile my code using -fcheck=all I get a runtime error since it seems I step out of bounds of my array dimension size. It comes from the part of my code shown below. I think it is because my loops over i,j only run from -ny to ny, -nx to nx but I try to use points at i+1,j+1,i-1,j-1 which takes me out of bounds in my arrays. When the loop over j starts at -ny, it needs j-1, so it immediately takes me out of bounds since I'm trying to access -ny-1. Similarly when j=ny, i=-nx,nx.
My question is, how can I fix this problem efficiently using minimal code?
I need the array grad(1,i,j) correctly defined on the boundary, and it needs to be defined exactly as on the right hand side of the equality below, I just don't know an efficient way of doing this. I can explicitly define grad(1,nx,j), grad(1,-nx,j), etc, separately and only loop over i=-nx+1,nx-1,j=-ny+1,ny-1 but this causes lots of duplicated code and I have many of these arrays so I don't think this is the logical/efficient approach. If I do this, I just end up with hundreds of lines of duplicated code that makes it very hard to debug. Thanks.
integer :: i,j
integer, parameter :: nx = 50, ny = 50
complex, dimension (3,-nx:nx,-ny:ny) :: grad,psi
real, parameter :: h = 0.1
do j = -ny,ny
do i = -nx,nx
psi(1,i,j) = sin(i*h)+sin(j*h)
psi(2,i,j) = sin(i*h)+sin(j*h)
psi(3,i,j) = sin(i*h)+sin(j*h)
end do
end do
do j = -ny,ny
do i = -nx,nx
grad(1,i,j) = (psi(1,i+1,j)+psi(1,i-1,j)+psi(1,i,j+1)+psi(1,i,j-1)-4*psi(1,i,j))/h**2 &
- (psi(2,i+1,j)-psi(2,i,j))*psi(1,i,j)/h &
- (psi(3,i,j+1)-psi(3,i,j))*psi(1,i,j)/h &
- psi(2,i,j)*(psi(1,i+1,j)-psi(1,i,j))/h &
- psi(3,i,j)*(psi(1,i,j+1)-psi(1,i,j))/h
end do
end do
If I was to do this directly for grad(1,nx,j), grad(1,-nx,j), it would be given by
do j = -ny+1,ny-1
grad(1,nx,j) = (psi(1,nx,j)+psi(1,nx-2,j)+psi(1,nx,j+1)+psi(1,nx,j-1)-2*psi(1,nx-1,j)-2*psi(1,nx,j))/h**2 &
- (psi(2,nx,j)-psi(2,nx-1,j))*psi(1,nx,j)/h &
- (psi(3,nx,j+1)-psi(3,nx,j))*psi(1,nx,j)/h &
- psi(2,nx,j)*(psi(1,nx,j)-psi(1,nx-1,j))/h &
- psi(3,nx,j)*(psi(1,nx,j+1)-psi(1,nx,j))/h
grad(1,-nx,j) = (psi(1,-nx+2,j)+psi(1,-nx,j)+psi(1,-nx,j+1)+psi(1,-nx,j-1)-2*psi(1,-nx+1,j)-2*psi(1,-nx,j))/h**2 &
- (psi(2,-nx+1,j)-psi(2,-nx,j))*psi(1,-nx,j)/h &
- (psi(3,-nx,j+1)-psi(3,-nx,j))*psi(1,-nx,j)/h &
- psi(2,-nx,j)*(psi(1,-nx+1,j)-psi(1,-nx,j))/h &
- psi(3,-nx,j)*(psi(1,-nx,j+1)-psi(1,-nx,j))/h
end do
One possible way for you could be using an additional index variable for the boundaries, modified from the original index to avoid getting out-of-bounds. I mean something like this:
do j = -ny,ny
jj = max(min(j, ny-1), -ny+1)
do i = -nx,nx
ii = max(min(i, nx-1), -nx+1)
grad(1,i,j) = (psi(1,ii+1,j)+psi(1,ii-1,j)+psi(1,i,jj+1)+psi(1,i,jj-1)-4*psi(1,i,j))/h**2 &
- (psi(2,ii+1,j)-psi(2,ii,j))*psi(1,i,j)/h &
- (psi(3,i,jj+1)-psi(3,i,jj))*psi(1,i,j)/h &
- psi(2,i,j)*(psi(1,ii+1,j)-psi(1,ii,j))/h &
- psi(3,i,j)*(psi(1,i,jj+1)-psi(1,i,jj))/h
end do
end do
It's hard for me to write a proper code because it seems you trimmed part of the original expression in the code you presented in the question, but I hope you understand the idea and apply it correctly for your logic.
Opinions:
Even though this is what you are asking for (as far as I understand), I would not recommend doing this before profiling and checking if assigning the boundary conditions manually after a whole array operation wouldn't be more efficient, instead. Maybe those extra calculations on the indices on each iteration could impact on performance (arguably less than if conditionals or function calls). Using "ghost cells", as suggested by #evets, could be even more performant. You should profile and compare.
I'd recommend you declaring your arrays as dimension(-nx:nx,-ny:ny,3) instead. Fortran stores arrays in column-major order and, as you are accessing values on the neighborhood of the "x" and "y", they would be non-contiguous memory locations for a fixed "other" dimension is the leftest, and that could mean less cache-hits.
In somewhat pseudo-code, you can do
do j = -ny, ny
if (j == -ny) then
p1jm1 = XXXXX ! Some boundary condition
else
p1jm1 = psi(1,i,j-1)
end if
if (j == ny) then
p1jp1 = YYYYY ! Some other boundary condition
else
p1jp1 = psi(1,i,j+1)
end if
do i = -nx, ny
grad(1,i,j) = ... term involving p1jm1 ... term involving p1jp1 ...
...
end do
end do
The j-loop isn't bad in that you are adding 2*2*ny conditionals. The inner i-loop is adding 2*2*nx conditionals for each j iteration (or 2*2*ny * 2*2*nx conditional). Note, you need a temporary for each psi with the triplet indices are unique, ie., psi(1,i,j+1), psi(1,i,j-1), and psi(3,i,j+1).

Excel VBA: How to concatenate variant array elements (row numbers) into a range object?

I did research this question but could not find the specific answer I was looking for and am actually even more confused at present.
I created a macro that would run through rows on a sheet and run boolean checks on a number of cells in each row that looked for the presence or absence of specific values, or calculated the outcome of a specific inequality. On the basis of those checks, the macro may or may not pass the row number into a specific array. That part is working fine.
My issue is, now that I have the row numbers (stored in variant arrays) - I cannot figure out how to properly concatenate that data into a range and then take a bulk excel action on those items. What I'd like to do is create a range of those values and then delete all of those rows at once rather than looping through.
My macro is on my work computer, but here's something I wrote that should explain what I'm trying to do:
Sub Test()
Dim Str As String
Dim r As Range
Dim i, a As Integer
Dim Count As Integer
Dim RngArray()
Count = ThisWorkbook.Sheets("Sheet1").Cells(Rows.Count, "A:A").End(xlUp).Row
ReDim RngArray(Count)
a = 0
For i = 1 To Count
If Not i = Count Then
RngArray(a) = i
Str = Str & RngArray(a) & ":" & RngArray(a) & ", "
a = a + 1
ElseIf i = Count Then
RngArray(a) = i
Str = Str & RngArray(a) & ":" & RngArray(a)
a = a + 1
Else: End If
Next i
Set r = Range(Str)'Error Can Appear here depending on my concatenation technique
Range(Str).EntireRow.Delete 'error will always appear here
End Sub
I've combined a few steps here and left out any Boolean checks; in my actual macro the values in the arrays are already stored and I loop from LBound to UBound and concatenate those values into a string of the form ("1:1, 2:2, 3:3, ...., n:n")
The reason why I did this is that the rows are all over the sheet and I wanted to get to a point where I could pass the argument
Range("1:1, 2:2, 3:3, ..., n:n").EntireRow.Delete
I think it's clear that I'm just not understanding how to pass the correct information to the range object. When I try to run this I get a "Method Range of Object Global" error message.
My short term fix is to just loop through and clear the rows and then remove all of the blank rows (the macro keeps track of absolute positions of the rows, not the rows after an iterative delete) - but I'd like to figure out HOW to do this my way and why it's not working.
I'm open to other solutions as well, but I'd like to understand what I'm doing wrong here. I should also mention that I used the Join() to try to find a workaround and still received the same type of error.
Thank you.
After some experimentation with my dataset for the macro above, I discovered that it worked on small sets of data in A:A but not larger sets.
I ran Debug.Print Len(Str) while tweaking the set size and macro and found that it appears Range() can only accept a maximum of 240 characters. I still don't understand why this is or the reason for the specific error message I received, but the macro will work if Len(Str) < 240.
I'll have to loop backwards through my array to delete these rows if I want to use my present method...or I may just try something else.
Thanks to Andrew for his attention to this!

Looping over array values in Lua

I have a variable as follows
local armies = {
[1] = "ARMY_1",
[2] = "ARMY_3",
[3] = "ARMY_6",
[4] = "ARMY_7",
}
Now I want to do an action for each value. What is the best way to loop over the values? The typical thing I'm finding on the internet is this:
for i, armyName in pairs(armies) do
doStuffWithArmyName(armyName)
end
I don't like that as it results in an unused variable i. The following approach avoids that and is what I am currently using:
for i in pairs(armies) do
doStuffWithArmyName(armies[i])
end
However this is still not as readable and simple as I'd like, since this is iterating over the keys and then getting the value using the key (rather imperatively). Another boon I have with both approaches is that pairs is needed. The value being looped over here is one I have control over, and I'd prefer that it can be looped over as easily as possible.
Is there a better way to do such a loop if I only care about the values? Is there a way to address the concerns I listed?
I'm using Lua 5.0 (and am quite new to the language)
The idiomatic way to iterate over an array is:
for _, armyName in ipairs(armies) do
doStuffWithArmyName(armyName)
end
Note that:
Use ipairs over pairs for arrays
If the key isn't what you are interested, use _ as placeholder.
If, for some reason, that _ placeholder still concerns you, make your own iterator. Programming in Lua provides it as an example:
function values(t)
local i = 0
return function() i = i + 1; return t[i] end
end
Usage:
for v in values(armies) do
print(v)
end

vba variant array bug

I am fairly new to excel vba and I can't seem to fix this problem with vbArrays. I created the function cumsum in vba just to make my life easier. However, I want to make the code flexible such that I can pass in both variants from a function and also a range. In my code, when I added the line vec=vec.value if I am passing in a range, it works perfectly fine but it doesn't work if I want it to work if I call the function and pass in a non range type. What I noticed was if I didn't have the line vec=vec.value in my code and I pass in a range, it has dimension 0 and I checked by writing my own function. Can someone please explain to me how I can fix this problem? Thanks.
Public Function cumsum(vec As Variant) As Variant
Dim temp() As Variant
MsgBox (getDimension(vec))
'works if i use vec=vec.value if vec is a range but has 0 if i do not vec = vec.values
ReDim temp(LBound(vec, 1) To UBound(vec, 1), 1 To 1) As Variant
Dim intCounter As Integer
For intCounter = LBound(vec) To UBound(vec)
If intCounter = LBound(vec) Then
temp(intCounter, 1) = vec(intCounter, 1)
Else
temp(intCounter, 1) = temp(intCounter - 1, 1) + vec(intCounter, 1)
End If
Next
cumsum = temp()
End Function
Function getDimension(var As Variant) As Integer
On Error GoTo Err:
Dim i As Integer
Dim tmp As Integer
i = 0
Do While True:
i = i + 1
tmp = UBound(var, i)
Loop
Err:
getDimension = i - 1
End Function
Why don't you just check the data type of vec by using VarType and TypeName then perform the necessary manipulation on vec
Public Function cumsum2(vec As Variant) As Variant
MsgBox TypeName(vec)
MsgBox VarType(vec)
cumsum2 = 0
End Function
The answers from #Jake and #chris are hints in the right direction, but I don't think they go far enough.
If you are absolutely sure that you'll only ever call this routine as a UDF (i.e. from formulas in your worksheets), then all you really need to do is add this:
If IsObject(vec) Then
Debug.Assert TypeOf vec Is Range
vec = vec.Value2
End If
to the start of your function. Called as a UDF, the only object type it should ever get passed is Range. Also, called as a UDF, you can rely on the fact that any arrays it gets passed will be indexed starting from 1.
I could pick out other problems with your routine, but they would be beside the point of your original question. Briefly: this will only work on column vectors, it will fail for single-cell ranges, etc.
Note that the reason your getDimension function is returning zero for Ranges because UBound is choking on the range. Your error handler happily catches an error (type mismatch) you didn't really expect to get and returning zero. (That method of finding "dimension" is assuming the error will be a subscript out range error.)
I wrote an answer a while back describing why, when working with Excel, I don't think the general getDimension approach is a good one:
https://stackoverflow.com/a/6904433/58845
Finally, the issue with VarType is that, when passed an object that has a default property, it will actually return the type of the property. So VarType(<range>) is going to tell you the type of the stuff in the range, not the code for object, because Range has a default property, Range.Value.
Modify your getDimension to include
If TypeName(var) = "Range" Then
var = var.Value
End If

Classic asp: Function call by reference doesn't work with an array

I have an array witch I pass to a function by reference to sort it. However, seems like the array is passed byval. Can anyone solve what's the problem? (Also sort workarounds accepted)
1) The script below passes an array by-reference to the sort function.
2) Sort function outputs the sorted array values.
3) The script outputs the sorted array values. However they are not sorted.
The script outputs:
300,200,100,,
100,200,300,
'declare variables
mitta(1) = 1
mitta(2) = 2
mitta(3) = 3
sort(mitta) ' see the function below
' show variables
For i = 1 To 3
response.write mitta(i) & ","
next
' sort function
function sort(byref a)
dim num,i,j,temp
num = ubound(a)+1
For i = 0 To num - 1
For j = i + 1 To num - 1
If a(i) < a(j) Then
temp = a(i)
a(i) = a(j)
a(j) = temp
End If
Next
Next
' show sorted variables
For i = 0 To num - 1
response.write a(i) & ","
a(i) = 0
next
end function
By wrapping mitta in parentheses in the function call sort(mitta), you're actually passing it by value, despite the function declaration. From http://blogs.msdn.com/b/ericlippert/archive/2003/09/15/52996.aspx:
The rules are
3.1) An argument list for a function call with an assignment to the
returned value must be surrounded by
parens: Result = MyFunc(MyArg)
3.2) An argument list for a subroutine call (or a function call with no
assignment) that uses the Call keyword
must be surrounded by parens: Call
MySub(MyArg)
3.3) If 3.1 and 3.2 do not apply then the list must NOT be surrounded by
parens.
And finally there is the byref rule:
arguments are passed byref when
possible but if there are “extra”
parens around a variable then the
variable is passed byval, not byref.
Now it should be clear why the
statement MySub(MyArg) is legal but
MyOtherSub(MyArg1, MyArg2) is not. The
first case appears to be a subroutine
call with parens around the argument
list, but that would violate rule 3.3.
Then why is it legal? In fact it is a
subroutine call with no parens around
the arg list, but parens around the
first argument! This passes the
argument by value. The second case is
a clear violation of rule 3.3, and
there is no way to make it legal, so
we give an error.
See also the MSDN reference for ByRef and ByVal Parameters. Instead, you should call sort either with:
sort mitta
or:
Call sort(mitta)
call sort(mitta)
That's it, just add the keyword call. Complete reference is available here.
BTW, your code has problems. The arrays are 0 based.
When you pass an object as parameter, you are passing a pointer to the object, not the object itself (this apply to all languages I know). So it does not matter if you pass it ByVal or ByRef, because by definition you are always passing a pointer (a reference to the object)

Resources