Hello I'm just wondering whether an 'if' statement can be considered as an Iteration. Because Iteration is used until a certain criteria is meet to allow the code to continue.
An iteration is one cycle (one time through) a loop. If blocks are not loops.
For blocks or While blocks are loops, and executing one cycle of the contents of one of those blocks — what's inside the For block or the While block — is one iteration of the loop. If the entirety of a For block or While block is a conditional If block, then the If block could be one iteration... but not because it's an If block, but because it's what's inside the loop.
No, an iteration is a repetition of some code. Once doesn't count as a repetition, else all code would be considered an iteration.
The If statement by itself cannot be considered iteration. You can run a code block containing an if statement as many times as you wish, but this doesn't make the if statement an iterator by itself. It's what calls that code block which could stand for iteration.
If statement has some similarities with those other conditional statements :
Select Case
Try Catch
Explicit iterators are the followings :
For/Next - For Each/Next
Do/Loop
While/End While
However, you have some logic that could turn the usage of an if statement as the trigger to resume or stop conditions of a loop, without the usage of direct loop statements like For or While. But that doesn't turn that if statement an iterator by itself, because the iteration is the combination of the if statement and a specific logic allowed by the programming language.
Recursion :
Private Function ResumeIncrement(ByRef Number As Int32) As Boolean
Number = Number + 1
If Number < 10 Then
Return ResumeIncrement(Number)
Else
Return False
End If
End Function
GoTo statement :
Private Sub TestGoTo()
Dim Number As Int32 = 0
IncrementMore:
Number = Number + 1
If Number < 10 Then
GoTo IncrementMore
End If
End Sub
And as stated by Joel Coehoorn above, the If block can contain anything, including iterator blocks triggered by For, While or Do. If you remove the contained block, that doesn't change anything in what the if part is supposed to do : a conditional check !
You can also do the inverse, and use an If block to control the way an iteration behaves. Like :
If SomeCondition Then
Exit For
' Or Exit While, etc. ie, using the 'Exit' statement
End If
or
i = 0
Do
i = i + 1
' ^^ capture i in Debug or Console
' Control the value of i...
If i Mod 2 = 0 Then
i = i - 2
Else
i = i * 3
End If
Loop While i < 30
' i = 1, 4, 3, 10, 9, 28, 27, [Exit Do with i = 81]
In the two examples above, the If statement is either there to break the iteration, or try again, that said, to "control the iteration", but in no means, to stand for iterator by itself.
Here is also another answer to clear out one uncertainty. You asked
Are if statements considered as an Iteration ?
(You made "if statements" plural) Then you said
Hello I'm just wondering whether an 'if' statement can be considered
as an Iteration. Because Iteration is used until a certain
criteria is meet to allow the code to continue.
(then you made it singular.. ???) That "...Iteration is used until..." should self answer your question alone, assuming you're iterating code blocks (which is the way it is understood here)
But what if you're talking about iterating some possible values until one meets your requirements..?
If MyValue = "" Then
' ...
ElseIf MyValue.StartsWith("A") Then
' ...
ElseIf MyValue.ToUpper = "TEST" Then
' ...
Else
' ...
End If
is similar to :
Select Case True
Case MyValue = "":
' ...
Case MyValue.StartsWith("A")
' blah blah blah
End Select
or
If MyValue = "" Then GoTo EmptyString
If MyValue.StartsWith("A") Then GoTo StartsWithA
If MyValue.ToUpper() = "TEST" Then GoTo IsTEST Else GoTo DoNothing
EmptyString:
' ...
GoTo DoNothing
StartsWithA:
' ...
GoTo DoNothing
IsTEST:
' ...
GoTo DoNothing
DoNothing:
' Resume...
Etc.
That's not an iteration ! That's a list of evaluation that are processed in order. When a condition evaluates to True, the related code is executed. Unlike iteration, this is just a matter of selecting the right path and resume, not walking the very same path several times (loops)
Yes, you're iterating through a collection of possible values (condition states) but NO, you're not iterating the If statement. You're only checking the result of each single If test once.
Related
I am developing an SSRS tablix report and struggling to remove some error messages from certain cells. The Expression in the cell is as follows:
=Iif(IsNothing(First(Fields!Details.Value)) , "", Join(Code.RemoveDuplicates(LookupSet(Fields!studyTitle.Value, Fields!studyTitle.Value, Fields!Link.Value ,"DataSet1")), vbCrLf ))
The custom function that's referenced is as follows:
Public Shared Function RemoveDuplicates(ByVal items As Object()) As String()
System.Array.Sort(items)
Dim k As Integer = 0
For i As Integer = 0 To items.Length - 1
If i > 0 AndAlso items(i).Equals(items(i - 1)) Then
Continue For
End If
items(k) = items(i)
k += 1
Next
Dim unique As [String]() = New [String](k - 1) {}
System.Array.Copy(items, 0, unique, 0, k)
Return unique
End Function
The problem I'm encountering is that cells in the output will display "#ERROR" if they are being run through the custom function with multiple blank values.
The goal of the initial IIF statement is to test those rows first to make sure that the function is only applied to those rows that have values, and the other cells should just be blank. The problem is that I'm still seeing errors for those cells, as if the false condition is applied anyway.
However, if I edit my IIF statement to have the same test condition but different true-part and false-part i.e.:
Iif(IsNothing(First(Details.Value)), "true", "false"))
Then suddenly I see exactly the rows I would expect to see marked true and false. If this is the case then I can't figure out where the errors are coming from in my initial statement.
I know the issue has to be somewhere with the RemoveDuplicates function because when I take that part out and just Join my duplicated values then I see no errors, and the false rows are the same as identified above.
Does anyone see something I'm missing that is generating this error or causing this seemingly inconsistent behavior from the IIf function?
Use If instead of IIf. The If construct shortcuts the evaluation, while the IIf evaluates all expressions.
=If(IsNothing(First(Fields!Details.Value)) , "", Join(Code.RemoveDuplicates(LookupSet(Fields!studyTitle.Value, Fields!studyTitle.Value, Fields!Link.Value ,"DataSet1")), vbCrLf ))
This is only available in VB.net and is not available in VBA. In VBA, you would use a standard If-Then-Else construct.
This question already has answers here:
AndAlso/OrElse in VBA
(10 answers)
Closed 8 years ago.
VBA doesn't short-circuit
VBA does not support short-circuiting - apparently because it only has bitwise And/Or/Not etc operations. From the VBA language specification: "Logical operators are simple data operators that perform bitwise computations on their operands." In this light, it makes sense that VBA was designed with true = &H1111 and false = &H0000: this way logical statements can be evaluated as bitwise operations.
The lack of short-circuiting can cause problems
Performance: the ReallyExpensiveFunction() will always be run when this statement is evaluated, even if it is not necessary by the result of the left hand side of the condition
If IsNecessary() And ReallyExpensiveFunction() Then
'...
End If
Errors: if MyObj is Nothing, this conditional statment will result in a runtime error because VBA will still try to check the value of Property
If Not MyObj Is Nothing And MyObj.Property = 5 Then
'...
End If
The solution I've used to implement short-cirtcuiting behavior is nested Ifs
If cond1 And cond2 Then
'...
End If
Becomes
If cond1 Then
If cond2 Then
'...
End If
End If
This way the If statements give the short-circuit-like behavior of not bothering to evaluate cond2 if cond1 is False.
If there is an Else clause, this creates duplicate code blocks
If Not MyObj Is Nothing And MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO"
End If
Becomes
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO" 'Duplicate
End If
Else
MsgBox "BOO" 'Duplicate
End If
Is there a way to rewrite If statements to preserve the short-circuit behavior, but avoid duplication of code?
Perhaps with another branching statement like Select Case?
To add context to the question, here is the specific case I'm looking at. I'm implementing a hash table that handles collisions by chaining them in a linked list. The underlying array size is enforced to be a power of two and the hashes are distributed into the current array size by truncating them to the appropriate length.
For example, suppose the array length is 16 (binary 10000). If I have a key that hashes to 27 (binary 11011), I can store it in my 16 slot array by keeping only the bits within the limit of that array size. The index where this item would be stored is (hash value) And (length of array - 1) which in this case is (binary 11011) And (1111) which is 1011 which is 11. The actual hash code is stored along with the key in the slot.
When looking up an item in the hash table in a chain, both the hash and the key must be checked to determine that the correct item has been found. However, if the hash doesn't match, then there is no reason to check the key. I was hoping to gain some tiny intangible amount of performance by nesting the Ifs to get the short-circuit behavior:
While Not e Is Nothing
If keyhash = e.hash Then
If Key = e.Key Then
e.Value = Value
Exit Property
Else
Set e = e.nextEntry
End If
Else
Set e = e.nextEntry
End If
Wend
You can see the Set... is duplicated, and thus this question.
As a more general apprach, I suggest to introduce condition flags and make usage of assigning comparison results to booleans:
dim cond1 as boolean
dim cond2 as boolean
cond1 = false
cond2 = false
' Step 1
cond1 = MyObj Is Nothing
' Step 2: do it only if step 1 was sucessful
if cond1 then
cond2 = MyObj.Property = 5
end if
' Final result:
if cond2 then
msgbox "Yay"
else
msgbox "Boo"
end if
By "chaining" those condition flags, every step is safe, you see the final result in the last condition flag and you don't do unnecessary comparisons. And, to me, it keeps readable.
EDIT 2014-07-09
I usually never omit block delimiters and I consequently set every statement of control structures on a new line. But in this case, you can carefully get a very dense notation that reminds on short-circuit notation, also because the VBA compiler initiates the variables:
dim cond1 as boolean
dim cond2 as boolean
dim cond3 as boolean
dim cond4 as boolean
cond1 = MyObj Is Nothing
if cond1 then cond2 = MyObj.Property = 5
if cond2 then cond3 = MyObj.Property2 = constSomething
if cond3 then cond4 = not isNull(MyObj.Property77)
if cond4 then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
I could agree to this. It's a clear flow to read.
EDIT 2021-03-21
Thanks to #Tom's comment, one can write it simpler:
dim cond as boolean
cond = MyObj Is Nothing
if cond then cond = MyObj.Property = 5
if cond then cond = MyObj.Property2 = constSomething
if cond then cond = not isNull(MyObj.Property77)
if cond then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
#Tom explains the advantages in his comment below. I fully agree with this. I can only imagine some situations while debugging, when I would like to have separated results of the conditions, and therefore explicitely with four different variables.
There is a way. You're not guaranteed to like it. But this is one of those carefully constructed cases where Goto comes in handy
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
Goto JUMPHERE
End If
Else
JUMPHERE:
MsgBox "BOO" 'Duplicate
End If
A short-circuited code to implement a short-circuited condition!
Alternately, if instead of MsgBox "BOO" is some long and convoluted code, it can be wrapped in a function and that can be written twice with minimal impact/overhead.
Regarding the specific use case, the multiple Set operations will have a minimal performance impact and hence, if one wants to avoid using Goto (still the most globally efficient approach, codesize + performance wise, avoiding creation of dummy variables, etc. - won't matter, though for such a small piece of code) there is negligible downside in simply repeating the command.
Just to analyze (your sample code) how much can be gained by different methods...
If both conditions are true:, there are 2 comparisons, 1 assignment, 0 jumps
If only first condition is true: there are 2 comparisons, 1 pointer-assignment, 1 jump
If only second condition is true: there is 1 comparison, 1 pointer-assignment, 1 jump
If both conditions are false: there is 1 comparison, 1 pointer-assignment, 1 jump (same as above)
In terms of performance, a jump is usually more expensive than comparison (which happens very quickly in ALU vs. the jump which could lead to a disruption in the code cache, maybe not at these sizes, but still jumps are expensive).
And normal assignment by value would be at best as fast as a pointer-assignment or sometimes worse (this is VBA, can't be 100% sure of the p-code implementation)
So, depending on your use case / expected data, you can try to minimize average number of jumps per iteration in your loop and reorder the code.
How about:
s = "BOO"
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then s = "YAY"
End If
MsgBox s
This question already has answers here:
AndAlso/OrElse in VBA
(10 answers)
Closed 8 years ago.
VBA doesn't short-circuit
VBA does not support short-circuiting - apparently because it only has bitwise And/Or/Not etc operations. From the VBA language specification: "Logical operators are simple data operators that perform bitwise computations on their operands." In this light, it makes sense that VBA was designed with true = &H1111 and false = &H0000: this way logical statements can be evaluated as bitwise operations.
The lack of short-circuiting can cause problems
Performance: the ReallyExpensiveFunction() will always be run when this statement is evaluated, even if it is not necessary by the result of the left hand side of the condition
If IsNecessary() And ReallyExpensiveFunction() Then
'...
End If
Errors: if MyObj is Nothing, this conditional statment will result in a runtime error because VBA will still try to check the value of Property
If Not MyObj Is Nothing And MyObj.Property = 5 Then
'...
End If
The solution I've used to implement short-cirtcuiting behavior is nested Ifs
If cond1 And cond2 Then
'...
End If
Becomes
If cond1 Then
If cond2 Then
'...
End If
End If
This way the If statements give the short-circuit-like behavior of not bothering to evaluate cond2 if cond1 is False.
If there is an Else clause, this creates duplicate code blocks
If Not MyObj Is Nothing And MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO"
End If
Becomes
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO" 'Duplicate
End If
Else
MsgBox "BOO" 'Duplicate
End If
Is there a way to rewrite If statements to preserve the short-circuit behavior, but avoid duplication of code?
Perhaps with another branching statement like Select Case?
To add context to the question, here is the specific case I'm looking at. I'm implementing a hash table that handles collisions by chaining them in a linked list. The underlying array size is enforced to be a power of two and the hashes are distributed into the current array size by truncating them to the appropriate length.
For example, suppose the array length is 16 (binary 10000). If I have a key that hashes to 27 (binary 11011), I can store it in my 16 slot array by keeping only the bits within the limit of that array size. The index where this item would be stored is (hash value) And (length of array - 1) which in this case is (binary 11011) And (1111) which is 1011 which is 11. The actual hash code is stored along with the key in the slot.
When looking up an item in the hash table in a chain, both the hash and the key must be checked to determine that the correct item has been found. However, if the hash doesn't match, then there is no reason to check the key. I was hoping to gain some tiny intangible amount of performance by nesting the Ifs to get the short-circuit behavior:
While Not e Is Nothing
If keyhash = e.hash Then
If Key = e.Key Then
e.Value = Value
Exit Property
Else
Set e = e.nextEntry
End If
Else
Set e = e.nextEntry
End If
Wend
You can see the Set... is duplicated, and thus this question.
As a more general apprach, I suggest to introduce condition flags and make usage of assigning comparison results to booleans:
dim cond1 as boolean
dim cond2 as boolean
cond1 = false
cond2 = false
' Step 1
cond1 = MyObj Is Nothing
' Step 2: do it only if step 1 was sucessful
if cond1 then
cond2 = MyObj.Property = 5
end if
' Final result:
if cond2 then
msgbox "Yay"
else
msgbox "Boo"
end if
By "chaining" those condition flags, every step is safe, you see the final result in the last condition flag and you don't do unnecessary comparisons. And, to me, it keeps readable.
EDIT 2014-07-09
I usually never omit block delimiters and I consequently set every statement of control structures on a new line. But in this case, you can carefully get a very dense notation that reminds on short-circuit notation, also because the VBA compiler initiates the variables:
dim cond1 as boolean
dim cond2 as boolean
dim cond3 as boolean
dim cond4 as boolean
cond1 = MyObj Is Nothing
if cond1 then cond2 = MyObj.Property = 5
if cond2 then cond3 = MyObj.Property2 = constSomething
if cond3 then cond4 = not isNull(MyObj.Property77)
if cond4 then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
I could agree to this. It's a clear flow to read.
EDIT 2021-03-21
Thanks to #Tom's comment, one can write it simpler:
dim cond as boolean
cond = MyObj Is Nothing
if cond then cond = MyObj.Property = 5
if cond then cond = MyObj.Property2 = constSomething
if cond then cond = not isNull(MyObj.Property77)
if cond then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
#Tom explains the advantages in his comment below. I fully agree with this. I can only imagine some situations while debugging, when I would like to have separated results of the conditions, and therefore explicitely with four different variables.
There is a way. You're not guaranteed to like it. But this is one of those carefully constructed cases where Goto comes in handy
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
Goto JUMPHERE
End If
Else
JUMPHERE:
MsgBox "BOO" 'Duplicate
End If
A short-circuited code to implement a short-circuited condition!
Alternately, if instead of MsgBox "BOO" is some long and convoluted code, it can be wrapped in a function and that can be written twice with minimal impact/overhead.
Regarding the specific use case, the multiple Set operations will have a minimal performance impact and hence, if one wants to avoid using Goto (still the most globally efficient approach, codesize + performance wise, avoiding creation of dummy variables, etc. - won't matter, though for such a small piece of code) there is negligible downside in simply repeating the command.
Just to analyze (your sample code) how much can be gained by different methods...
If both conditions are true:, there are 2 comparisons, 1 assignment, 0 jumps
If only first condition is true: there are 2 comparisons, 1 pointer-assignment, 1 jump
If only second condition is true: there is 1 comparison, 1 pointer-assignment, 1 jump
If both conditions are false: there is 1 comparison, 1 pointer-assignment, 1 jump (same as above)
In terms of performance, a jump is usually more expensive than comparison (which happens very quickly in ALU vs. the jump which could lead to a disruption in the code cache, maybe not at these sizes, but still jumps are expensive).
And normal assignment by value would be at best as fast as a pointer-assignment or sometimes worse (this is VBA, can't be 100% sure of the p-code implementation)
So, depending on your use case / expected data, you can try to minimize average number of jumps per iteration in your loop and reorder the code.
How about:
s = "BOO"
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then s = "YAY"
End If
MsgBox s
Alright, so I am editing a Macro for Reflection Workspace Terminal Emulator. I have a macro (it is long and not necessarily relevant, I can post the full code if anyone wants, it is about a page)
At the very end of the macro, a "Good Morning" Message is printed, and then also the value of a string variable named myName. This works fine.
What I want to do is then use a For loop to print a number of control characters (tab) equal to the amount printed in the Good Morning User. Here is the code I have so far:
Dim X As Integer
For i = 1 To i = (13 + Len(myName)) 'Good Morning + a space character will always = 13
ibmCurrentScreen.SendKeys (ControlKeyCode_Tab)
Next i
End Sub
I would use the Chr() function to print the control characters, but it seems that this particular program uses ControlKeyCode_X to print them.
The For...Next loop in VBA is a simple single variable counter based loop and cannot have complex criteria or multiple counters like in some other languages (e.g. in C# for(int i = 0, int j = 10; i < 10; i++, j--)). In VBA, therefore, the i = after To is redundant because it cannot be anything other than i.
The 'For' loop in VBA therefore only specifies the counter once. It should be:
For i = startVal To endVal [step incrementVal]
...
Next [i]
i = variable to vary
startVal = start value
endVal = end value (inclusive, i.e. To 10 will include 10 as the final value)
incrementVal = optional value to increment/decrement by each loop
(default = 1)
The counter is always incremented/decremented at the end of each loop then evaluated against endVal (i.e. for a vanilla increment by one loop, after exiting the loop it will be endValue + 1).
Note (unrelated to your situation) that you can change the value of i in the loop, e.g. to increment twice because of some special circumstance.
Specifying the variable with the Next statement is optional and has no effect on behaviour (any nested loop must always be closed before an outer loop) but is good practice for reference when you have many For...Next loops so it is clear which loop the Next is closing (although you should be religiously indenting or otherwise delineating your code)
So in your case For i = 1 To (13 + Len(myName)) instead.
I have a For-Next loop. Within the loop I test several different criteria and if any test fails, then I am ready at that point to skip the remainder of the code in the loop and advance to the "next" item. The way I currently handle this is with a GoTo statement that takes me to the line right before "Next". I'd prefer not to use a GoTo statement, is there another way to advance to the "next" item from within a For-Next loop? TIA!
For x = 1 to 10
Test 1
If Test 1 fails then
GoTo Line1
End if
Test 2
If Test 2 fails then
GoTo Line1
End if
.
.
.
If all tests pass then
add item to array
End if
Line1:
Next
Unfortunately there is no continue-like statement in a for loop in vba. (The related control structure Exit For does exist but that's of no help here).
And it's good that you have reservations on using a GoTo: they do make code hard to follow.
Your best bet is to put the code in the loop in a separate function and use Exit Function within that function at appropriate points. You can even then relay error codes back to the caller so helping the code to scale.
Here's a workaround for the lack of a continue keyword:
For x = 1 to 10
Do
Test 1
If Test 1 fails then
Exit Do
End if
Test 2
If Test 2 fails then
Exit Do
End if
.
.
.
If all tests pass then
add item to array
End if
Loop While False
Next
You can use if else ladder :
For x = 1 to 10
if test 1
else if test 2
else if test 3
.
.
.
.
else
add item to array
end if
Next
Besides GoTo there is not any direct way to jump in between your code,
Hope this might help.
If you don't have too many tests, you could you use the Not condition and build a nested If statement. This should have almost the same effect as what you're asking for, since any failed test would end that If statement and move the code to the next X in your loop without executing the following tests.
Here's an example of what I mean -- a two test loop that builds an array containing the numbers 5 through 10:
Sub TestConditionalPass()
Dim intX As Integer
Dim intArray() As Integer
ReDim intArray(1)
For intX = 1 To 10
If Not intX -2 < 1 Then
If Not intX * 2 < 10 Then
ReDim Preserve intArray(UBound(intArray) + 1)
intArray(UBound(intArray)) = intX
End If
End If
Next intX
End Sub
Since you are preforming an action only if all tests succeed, then do the if-statement with ands. VBA doesn't short circuit the tests, (i.e. it will evaluate each test case, even if the first one returns false.) I would suggest encapsulating each test in a function that returns a Boolean to keep your code tidy.
If Test1() and _
Test2() and _
testn() THEN
add item to array
End if
Another way is to make use of a Boolean variable in your code, like this
Dim Success as Boolean
Success=True
Test 1
If Test 1 fails then
Success=false
End if
Test 2
If Test 2 fails then
Success=false
End if
.
.
.
If Success then
add item to array
End if
If the tests are expensive, you can add an inner if statement checking to see if we need to evaluate the next test like this
If Success Then
If Test n fails then
Success=false
End If
End if