Multiple True conditions in an IF statement in VB - vb.net

Is it possible to have multiple conditions that can evaluate to true in an if statement? Primarily just shorthand to save some time:
Example:
if value = 1 or value = 9 or value = 3 or value = 17 Then return True
Im wondering if there is something to this effect (could not find in MSDN)
if value = 1,9,3,17 Then return True

Can't do that with an IF statement, but you could use:
SELECT CASE value
CASE 1, 3, 9, 17
' Do something
CASE 2, 4
' Do another thing
CASE ELSE
' Do something else
END SELECT

You can also try like:
If (New List(Of Integer)(New Integer() {1, 3, 5, 7, 9}).Contains(value)) Then
Return TRUE
End If

If it's a return statement, you could make it bit shorter like this;
Return value = 1 Or value = 9 Or value = 3 Or value = 17
Also if the values would be in an array, you could use Linq - Any function;
Dim value As Integer = 1
Dim values = New Integer() {1, 9, 3, 17}
Return values.Any(Function(number) number = value)

You can't do what you want exactly within VB, but you can get around it. Here is one method. Put your values into an Array. You can search the Array using the IndexOf method. If the IndexOf returns a number above -1, then the item was found in the array. There are some gotchas that you will want to read about on the MSDN page.
Dim value As Boolean
Dim myList() As Integer = {1, 9, 3, 17}
If Array.IndexOf(myList, 12) > -1 Then
value = True
Else
value = False
End If
Console.WriteLine("Found: {0}", value)
You can also shorten the test to an inline comparison and assignment, removing the need for the If statement above.:
value = (Array.IndexOf(myList, 12) > -1)

Related

VBA create dictionary aggregate values

I have six variables, symbolizing three pairs (key/value). It will always be three pairs.
cb1 = 1: cb1value = 10
cb2 = 1: cb2value = 20
cb3 = 8: cb3value = 10
What I'm failing at is aggregating the values in a dictionary according to the key.
So in the above case the result would be:
1, (10, 20)
8, (10)
The end goal here is to use Sum(key) to get the total per key.
EDIT: Thanks for the replies so far. Maybe I'm just thinking too complicated. First I've put all the values in an array and then loop through it.
MyArray = Array(cb1, cb1value, cb2, cb2value, cb3, cb3value)
Now the keys are every 2 steps, so in my loop:
For i = 0 To 5 Step 2
If Not (keywords.Exists(MyArray(i))) Then
keywords.Add MyArray(i), Collection(MyArray(i + 1))
Else
'If the key exists, the value should be added to the existing key's collection. **But how?**
End If
Next i
For i = lbound(MyArray) To UBound(MyArray)-1 Step 2
If Not (keywords.Exists(MyArray(i))) Then keywords.Add MyArray(i), New Collection
keywords(MyArray(i)).Add MyArray(i + 1)
Next i
To get the sum of all entries in a collection:
Function SumCollection(col as Collection)
Dim rv As Double, i
For Each i in col
rv = rv + i
Next i
SumCollection = rv
End Function
If all you need is the sum though, you don't need the collection: just sum directly in the dictionary as you add each item.

Sorting List(Of T) stopping early

I have a custom class called SaveFile. One property it has is SaveNumber and I'm trying to sort the list by that property. I read a bunch of articles here and got it to sort properly but it's stopping early? The case in which I noticed this is with set that has 79 saves. It would sort: 1, 2, 3, 4, 5, 6, 7, 10, 11, ... 30, 8, 31, ... 70, 9, 71, .. The code I use is
saveList.Sort(Function(x, y) x.CompareTo(y))
But if I use the code:
For i = 0 To 3
saveList.Sort(Function(x, y) x.CompareTo(y))
Next
then it sorts right but it takes a very long time and one set has over 700 SaveFiles in it so it takes almost 5 minutes for my program to load. Any ideas?
This is the code that I have for my CompareTo function:
Public Function CompareTo(y As SaveFile) As Integer Implements IComparable(Of SaveFile).CompareTo
'If neither are an autosave then compare save number
If Not Me.Text.StartsWith("autosave") And Not y.Text.StartsWith("autosave") Then
If Me.SaveNumber.ToString.Length > y.SaveNumber.ToString.Length Then
Return False
ElseIf Me.SaveNumber.ToString.Length < y.SaveNumber.ToString.Length Then
Return True
End If
Return Me.SaveNumber < y.SaveNumber
'if either is an autosave
Else
'Create to comparable integers with
'a value of 4. We set the value to
'4 because it is higher than the 3
'available autosaves, making it sort
'after any autosaves if only one is
'an autosave.
Dim xComp As Integer = 4
Dim yComp As Integer = 4
'If x is an autosave then figure out
'which autosave number it is.
If Me.Text.StartsWith("autosave") Then
Select Case True
Case Me.Text.EndsWith("1")
xComp = 1
Case Me.Text.EndsWith("2")
xComp = 2
Case Me.Text.EndsWith("3")
xComp = 3
End Select
End If
'if y is an autosave then figure out
'which autosave number it Is.
If y.Text.StartsWith("autosave") Then
Select Case True
Case y.Text.EndsWith("1")
yComp = 1
Case y.Text.EndsWith("2")
yComp = 2
Case y.Text.EndsWith("3")
yComp = 3
End Select
End If
Return xComp < yComp
End If
End Function
First, asking 2 questions in one post is not a good idea because it reduces the chances someone will know the answer to both. For example, without seeing the code that loads these things or even what they are, makes it just a guess why it takes so long to load.
For the sorting, your CompareTo method is flawed. From MSDN:
Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
That is, it should return -1 (precedes), 1 (follows) or 0 (same). Yours just returns Boolean:
If Me.SaveNumber.ToString.Length > y.SaveNumber.ToString.Length Then
Return False
ElseIf Me.SaveNumber.ToString.Length < y.SaveNumber.ToString.Length Then
Return True
End If
...
Return xComp < yComp
We have no idea what type SaveNumber is, but if it is am integer, comparing the length of the string form if it is not the same as comparing the value:
' Me vs Other
Return "9".Length < "7".Length
That will return False (0) when it by value, it should return 1. Turning on Option Strict would have flagged the incorrect return type, which might have led you to the answer. It should be something like this, ignoring the "auto" logic:
Public Function CompareTo(other As SaveItem) As Integer _
Implements IComparable(Of SaveItem).CompareTo
... special handling for "auto"
If Me.SaveNumber = other.SaveNumber Then
Return 0
ElseIf Me.SaveNumber < other.SaveNumber Then
Return -1
Else
Return 1
End If
End Function
But, you may not even need that (assuming again that SaveNumber is an int):
saveItems = saveItems.OrderBy(Function(q) q.SaveNumber).ToList()
IComparable.CompareTo() might be needed in order to handle the "autosave" logic but it might be able to be handled in the OrderBy() depending on the information the class exposes.

Conditionally skip cases

In a Select...Case statement, is there a way to skip cases based on a precondition?
What I'm doing now, using an incredibly stupid example:
Private Sub PrintNumbers(includeEvenNumbers As Boolean, includeOddNumbers As Boolean)
For number As Integer = 0 To 9
Select Case number
Case 0, 2, 4, 6, 8
If includeEvenNumbers Then
Console.WriteLine(number)
End If
Case 1, 3, 5, 7, 9
If includeOddNumbers Then
Console.WriteLine(number)
End If
End Select
Next
End Sub
Sometimes I'll even write my cases inside out:
Private Sub PrintNumbers(includeEvenNumbers As Boolean, includeOddNumbers As Boolean)
For number As Integer = 0 To 9
Select Case True
Case includeEvenNumbers
If number Mod 2 = 0 Then
Console.WriteLine(number)
End If
Case includeOddNumbers
If number Mod 2 <> 0 Then
Console.WriteLine(number)
End If
End Select
Next
End Sub
What I'd really like to do instead:
Private Sub PrintNumbers(includeEvenNumbers As Boolean, includeOddNumbers As Boolean)
For number As Integer = 0 To 9
Select Case number
Case 0, 2, 4, 6, 8 When includeEvenNumbers
Console.WriteLine(number)
Case 1, 3, 5, 7, 9 When includeOddNumbers
Console.WriteLine(number)
End Select
Next
End Sub
Notice that I used the When keyword, which is currently only used in Try...Catch blocks.
Can this be done? Who do I talk to to make this happen?
EDIT (1/2)
What's important is that this code would first evaluate When <expression>. Only if that evaluates to True, it would go on to evaluate the Case <expression>.
The main reason why I want to do this is because I'd like to write cases where the test condition throws an exception if the circumstances are right (or wrong, depending on how you look at it). I'd like to skip those cases if the precondition is true.
EDIT (2/2)
It's pretty clear by now that what I'm asking for is not possible in the current iteration of VB. So I searched for a place to submit feature requests to the .NET development team, then found out they have a uservoice platform.
Long story short: if you'd like to see this implemented, tap the vote button on this page: https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4274712-add-when-keyword-support-to-select-case-stat
example of linq:
Dim condition As Predicate(Of Integer) = Nothing
If includeEvenNumbers Then
condition = Function(x) x Mod 2 = 0
Else
condition = Function(x) x Mod 2 = 1
End If
For Each item In Enumerable.Range(0, 8).Where(condition)
' do what ever you like here...
Next
EDIT: to make it simpler:
For Each item In Enumerable.Range(0, 8).Where(Function(x) x Mod 2 = If(includeEvenNumbers, 0, 1))
' do what ever you like here...
Next
By using an enum instead of 2 booleans you only need one if statement to accomplish a 2 condition check. Something like this:
Public Enum Include
Odd = 1
Even = 2
Both = 0
End Enum
Private Sub PrintNumbers(includenumbers As Include)
For I = 1 To 10
If includenumbers = Include.Both OrElse I Mod 2 = includenumbers Mod 2 Then
TextBox2.AppendText(I.ToString)
End If
Next
End Sub
You would call it like this:
PrintNumbers(Include.Odd)

Ordering a VB dictionary by value, then looping through the ordered values

I have a dictionary of (string, integer). I need to order the dictionary by integer first, then use each integer value in a loop.
For instance, the dictionary contains cat 2, dog 1, rat 3...ordered would be dog 1, cat 2, rat 3. Then I would get the first value, 1, perform some functions with it, get the next value 2, perform some functions with it, and so on until the end of the dictionary.
So far I have:
Dim ordered = newdictionary.OrderBy(Function(x) x.Value)
ordered.Select(Function(x) x.Value)
What is a good way to accomplish this?
This seems to be what you actually want:
For Each value In newdictionary.Values.OrderBy(Function(i) i)
' do something with the value '
Next
Now you're looping the ordered int values of the dictionary
Dictionary<TKey, TValue>.Values Property
Edit according to your comment you want to include the index to check if the next element equals the current:
Dim values = newdictionary.Values.
Select(Function(i, index) New With {.Num = i, .Index = index}).
OrderBy(Function(x) x.Num)
For Each value In values
Dim nextElement = values.ElementAtOrDefault(value.Index + 1)
If nextElement Is Nothing OrElse nextElement.Num <> value.Num Then
' next value is different or last element
Else
' next number same
End If
Next

VB.NET - How to use array literal in a condition?

I just learn how to create an array literal in VB.NET.
Dim MyArray = New Integer() { 1, 2, 3 }
' Or
Dim MyArray() As Integer = { 1, 2, 3 }
' Or
Dim MyArray() = { 1, 2, 3 }
' Or
Dim MyArray() = { 1, 2, "A", "B" }
Now, I want to use A LITERAL ARRAY in a condition (see pseudo-code)
If 1 exists in {1,2,3,4} Then
MsgBox "Exists!"
End If
but I don't know how, seems like you have to assign it to a variable before you can use it in the condition.
Dim MyArray() As Integer = {3, 2, 3}
If (MyArray.Contains(1)) Then
MsgBox("exists!")
Else
MsgBox("does not exist!")
End If
The above code works, but I'm just wondering is there any way to do this without assigning the array literal to variable first?
Thanks in advance!
Use {1,2,3,4}.Contains(1) for this.