Is it possible to do a For...Each Loop Backwards? - vb.net

I don't believe this is possible by conventional methods, but something like this verbose code:
For Each s As String In myStringList Step -1
//' Do stuff here
Next
I will probably have to invert the myString object before a conventional For..Each Loop, correct?

I think the documentation referenced in Mike's answer below is extremely misleading. The order of For Each is defined by the collection it's called (i.e. its implementation of IEnumerable/IEnumerable<T>), but that's not the same as saying it shouldn't be used when the order is important. Many collections (such as arrays, List<T> etc) always go in the "natural" order.
Part of the documentation does allude to this:
Traversal Order. When you execute a
For Each...Next loop, traversal of the
collection is under the control of the
enumerator object returned by the
GetEnumerator method. The order of
traversal is not determined by Visual
Basic, but rather by the MoveNext
method of the enumerator object. This
means that you might not be able to
predict which element of the
collection is the first to be returned
in element, or which is the next to be
returned after a given element.
That's not at all the same as saying it can't be relied upon - it can be relied upon if you know that the collection you're iterating over will produce the results in the desired order. It's not like it's going to pick elements at random. The behaviour in terms of IEnumerable/IEnumerable<T> is clearly defined on that page.
The most important exceptions to predictable orderings are dictionaries and sets, which are naturally unordered.
To reverse an IEnumerable<T>, use Enumerable.Reverse - but if you need to iterate in reverse over a collection which is indexed by position (such as an array or List<T>) then it would be more efficient to use a For loop starting at the end and working backwards.

You'd have to do something like:
For i as integer = myStringList.Count-1 to 0 step -1
dim s as string = myStringList.Item(i)
' Do your stuff with s
Next i
But as far as I know, you can't do a "For...Each" backwards, though that would be nice in a few instances.

Sadly, the MSDN docs on For Each state that the For Each construct is there explicitly for cases where the order of the iteration is unimportant (unordered sets and the like). So there is unfortunately no concept of reversing a For Each, as the order is not defined anyway.
Good luck!

Call the System.Linq.Enumerable.Reverse method to get an IEnuemrable(Of TSource) in the reverse order of your enumerable source.

Testig in Framework 4, the code
For Each s As String In myStringList.Reverse
//' Do stuff here
Next
it wasn't working, the right way to do it is:
myStringList.Reverse() ' it is a method not a function
For Each s As String In myStringList
//' Do stuff here
Next
Look at: MSDN: Reserve

For Each s As String In myStringList.Reverse
//' Do stuff here
Next

The reason it can't be done is that, as a basic design feature, For Each can iterate over an enumerable without a last element, or with an as yet unknown last element.

This works:
Dim myArray() As String = {"1", "2", "3", "4", "5"}
For Each n As String In myArray.Reverse
Debug.Print(n)
Next
Output: 5 4 3 2 1
'
This also works:
Dim myArray() As String = {"1", "2", "3", "4", "5"}
For i As Integer = myArray.Length - 1 To 0 Step -1
Debug.Print(myArray(i))
Next
Output: 5 4 3 2 1

What you have to do is create an array with your for each you had before, then use array.reverse and run the for each on the array. Done
Cheers

Depending on what happens inside your loop you can do an .InsertAt(object,0) instead of an .Add and produce the same result as a reverse enumeration.

You can add a extended function to the class you are trying to reverse
<Serializable()> Public Class SomeCollection
Inherits CollectionBase
Public Sub New()
End Sub
Public Sub Add(ByVal Value As Something)
Me.List.Add(Value)
End Sub
Public Sub Remove(ByVal Value As Something)
Me.List.Remove(Value)
End Sub
Public Function Contains(ByVal Value As Something) As Boolean
Return Me.List.Contains(Value)
End Function
Public Function Item(ByVal Index As Integer) As Something
Return DirectCast(Me.List.Item(Index), Something)
End Function
Public Function Reverse() As SomeCollection
Dim revList As SomeCollection = New SomeCollection()
For index As Integer = (Me.List.Count - 1) To 0 Step -1
revList.List.Add(Me.List.Item(index))
Next
Return revList
End Function
End Class
Then you would call it like this
For Each s As Something In SomeCollection.Reverse
Next

first you should create a list(of string) in for each statement, and after that make another normal for .. step -1 ..next statement. see an example:
Dim ff As New List(Of Integer)
For Each rRow As DataRow In DeletedRows
Dim item As Integer
item = rRow.Table.Rows.IndexOf(rRow)
ff.Add(item)
Next
For i As Integer = ff.Count - 1 To 0 Step -1
dim item as integer=ff(i)
next i
i hope that be helpful

The accepted answer explains why, but to add another example, for a collection with key (SortedList, SortedDictionary, Dictionary, etc.) you can do
For Each Id as Tkey in MyCollection.Keys.Reverse
// The item in question is now available as MyCollection(Id)
Next

' create a list of whatever and step through For Loop Collecting
dim revList as New List (of ToolStripItem)
For each objItem as ToolStripItem in Menu.DropDownItems
revList.Add(objItem)
next
revList.reverse ' reverse the order of that list
' step through the reverse ordered list
for each objItem as ToolStripItem in revList
' do whatever (like removing your menu from an ArrayMenu)
next
' replace ToolStripItem and Menu.DropDownItems with whatever you need

Related

Getting the value of string from a jagged array when using option strict

I've just started using option strict (based on advice from another question) and I'm stuck!
I have an array which contains more arrays. The second array contains strings. I can't work out how to get the values of the strings out of the second array.
dim sb as new stringbuilder
public sub foobar({{"abcd", "efg"},{"hjik", "lmnop"}}
for each arr in master
sb.AppendLine(arr(0))
next
end sub
But I get a late binding error. I understand why I'm getting the error but how do I get around it?
Assuming master looks similar to what you're passing in to foobar, then arr(0) get's you to element 0 of the first dimension of the array, you'd need to iterate over that array if you want to append all elements from each second level array.
With Option Strict you will also need to declare the type of each variable, so it is going to get pretty "wordy":
Dim aryOfArrays()() As String = _
New String()() {New String() {"1a", "1b"}, New String() {"2a", "2b"}}
For Each aryOfStrings As String() In aryOfArrays
For Each strElement As String In aryOfStrings
sb.AppendLine(strElement)
Next
Next
https://msdn.microsoft.com/en-us/library/kfky451c(v=vs.90).aspx
You should define variables in vb.net in the begging of the function.
Move the "dim str" to the beginning of function
more details

How Do I loop through this class once I have added items

How do i loop through this class once I add items via this method. Just I am quite new to generic lists so was wonding if someone could point me in right direction in datatables im used to doing the following:
For Each thisentry In dt.rows
Next
What do I use in collections
Calling Code
Calling this in my delciarations of main class
Dim infoNoProductAvail As List(Of infoProductsNotFound) = New List(Of infoProductsNotFound)()
this is how i am adding the files but I have checked in the routine and the count for the list is at 2 products
If medProductInfo.SKU.SKUID = 0 Then
infoNoProductAvail.Add(New infoProductsNotFound(thisenty2.Item("EAN13").ToString(), True))
End If
this is the class itselfs
Public Class infoProductsNotFound
Public Sub New(tbcode As String, notfound As Boolean)
Me.tagbarcode = tbcode
Me.notfound = notfound
End Sub
Private tagbarcode As String = String.Empty
Private notfound As Boolean
Public Property tbcode() As String
Get
Return tagbarcode
End Get
Set(ByVal value As String)
tagbarcode = value
End Set
End Property
Public Property isNotFound() As Boolean
Get
Return notfound
End Get
Set(ByVal value As Boolean)
notfound = value
End Set
End Property
End Class
Tried
I tried using the following
Function BuildExceptionsForEmail()
Dim retval As String = ""
Dim cnt As Int32 = 0
retval = "The following products are not avialable" & vbCrLf
For Each info As infoProductsNotFound In infoNoProductAvail
retval &= info.tbcode
cnt &= 1
Next
Return retval
but for some reason at this point my info noproductAvail is blank even though in the routine above its sitting at count of 2 what gives?
First I'd shrink that declaration a bit:
Dim infoNoProductAvail As New List(Of infoProductsNotFound)
Next, to iterate there are several options. First (and what you're likely most used to):
For Each info as infoProductsNotFound in infoNoProductAvail
If info.tbCode = "xyz" Then
DoSomething(info)
End If
Next
Or you might want to use lambda expressions (if you're using .Net 3.5 and above I think - might be .Net 4):
infoNoProductAvail.ForEach (Function(item) DoSomething(item))
Remember that generics are strongly typed (unlike the old VB collections) so no need to cast whatever comes out: you can access properties and methods directly.
If infoNoProductAvail(3).isNotFound Then
'Do something
End If
(Not that that is a great example, but you get the idea).
The For Each syntax is the same. It works the same way for all IEnumerable objects. The only "trick" to it is to make sure that your iterator variable is of the correct type, and also to make sure that you are iterating through the correct object.
In the case of the DataTable, you are iterating over it's Rows property. That property is an IEnumerable object containing a list of DataRow objects. Therefore, to iterate through it with For Each, you must use an iterator variable of type DataRow (or one of its base classes, such as Object).
To iterate through a generic List(Of T), the IEnumerable object is the List object itself. You don't need to go to one of it's properties. The type of the iterator needs to match the type of the items in the list:
For Each i As infoProductsNotFound In infoNoProductAvail
' ...
Next
Or:
Dim i As infoProductsNotFound
For Each i In infoNoProductAvail
' ...
Next
Or:
For Each i As Object In infoNoProductAvail
' ...
Next
Etc.

Checking if a value is a member of a list

I have to check a piece of user input against a list of items; if the input is in the list of items, then direct the flow one way. If not, direct the flow to another.
This list is NOT visible on the worksheet itself; it has to be obfuscated under code.
I have thought of two strategies to do this:
Declare as an enum and check if input is part of this enum, although I'm not sure on the syntax for this - do I need to initialise the enum every time I want to use it?
Declare as an array and check if input is part of this array.
I was wondering for VBA which is better in terms of efficiency and readability?
You can run a simple array test as below where you add the words to a single list:
Sub Main1()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test("dog") 'True
Debug.Print "horse", Test("horse") 'False
End Sub
Function Test(strIn As String) As Boolean
Test = Not (IsError(Application.Match(strIn, arrList, 0)))
End Function
Or if you wanted to do a more detailed search and return a list of sub-string matches for further work then use Filter. This code would return the following via vFilter if looking up dog
dog, dogfish
In this particular case the code then checks for an exact match for dog.
Sub Main2()
arrList = Array("cat", "dog", "dogfish", "mouse")
Debug.Print "dog", Test1("dog")
Debug.Print "horse", Test1("horse")
End Sub
Function Test1(strIn As String) As Boolean
Dim vFilter
Dim lngCnt As Long
vFilter = Filter(arrList, strIn, True)
For lngCnt = 0 To UBound(vFilter)
If vFilter(lngCnt) = strIn Then
Test1 = True
Exit For
End If
Next
End Function
Unlike in .NET languages VBA does not expose Enum as text. It strictly is a number and there is no .ToString() method that would expose the name of the Enum. It's possible to create your own ToString() method and return a String representation of an enum. It's also possible to enumerate an Enum type. Although all is achievable I wouldn't recommend doing it this way as things are overcomplicated for such a single task.
How about you create a Dictionary collection of the items and simply use Exist method and some sort of error handling (or simple if/else statements) to check whether whatever user inputs in the input box exists in your list.
For instance:
Sub Main()
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
myList.Add "item1", 1
myList.Add "item2", 2
myList.Add "item3", 3
Dim userInput As String
userInput = InputBox("Type something:")
If myList.Exists(userInput) Then
MsgBox userInput & " exists in the list"
Else
MsgBox userInput & " does not exist in the list"
End If
End Sub
Note: If you add references to Microsoft Scripting Runtime library you then will be able to use the intelli-sense with the myList object as it would have been early bound replacing
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
with
Dim myList as Dictionary
Set myList = new Dictionary
It's up to you which way you want to go about this and what is more convenient. Note that you don't need to add references if you go with the Late Binding while references are required if you want Early Binding with the intelli-sense.
Just for the sake of readers to be able to visualize the version using Enum let me demonstrate how this mechanism could possibly work
Enum EList
item1
item2
item3
[_Min] = item1
[_Max] = item3
End Enum
Function ToString(eItem As EList) As String
Select Case eItem
Case EList.item1
ToString = "item1"
Case EList.item2
ToString = "item2"
Case EList.item3
ToString = "item3"
End Select
End Function
Function Exists(userInput As String) As Boolean
Dim i As EList
For i = EList.[_Min] To EList.[_Max]
If userInput = ToString(i) Then
Exists = True
Exit Function
End If
Next
Exists = False
End Function
Sub Main()
Dim userInput As String
userInput = InputBox("type something:")
MsgBox Exists(userInput)
End Sub
First you declare your List as Enum. I have added only 3 items for the example to be as simple as possible. [_Min] and [_Max] indicate the minimum value and maximum value of enum (it's possible to tweak this but again, let's keep it simple for now). You declare them both to be able to iterate over your EList.
ToString() method returns a String representation of Enum. Any VBA developer realizes at some point that it's too bad VBA is missing this as a built in feature. Anyway, you've got your own implementation now.
Exists takes whatever userInput stores and while iterating over the Enum EList matches against a String representation of your Enum. It's an overkill because you need to call many methods and loop over the enum to be able to achieve what a simple Dictionary's Exists method does in one go. This is mainly why I wouldn't recommend using Enums for your specific problem.
Then in the end you have the Main sub which simply gathers the input from the user and calls the Exists method. It shows a Message Box with either true or false which indicates if the String exists as an Enum type.
Just use the Select Case with a list:
Select Case entry
Case item1,item2, ite3,item4 ' add up to limit for Case, add more Case if limit exceeded
do stuff for being in the list
Case Else
do stuff for not being in list
End Select

VB.Net Extract numbers from string function

My request is one that can extract a number somewhat by a search.
Example:
animalsOwned|4 would return an containing "4"
animals|3|2|1|3 would return an array containing "3", "2", "1", "3"
This would make it easier for me during a file stream reader.
Thank you
Dim astring = "ABCDE|1|2|3|4"
Dim numbers = (From s In astring
Where Char.IsDigit(s)
Select Int32.Parse(s)).ToArray()
This LINQ statement should help. It simply checks each character in a string to see if it's a digit. Note that this only applies to single digit numbers. It becomes a bit more complicated if you want "ABC123" to return 123 vs. 1, 2, 3 array.
Try regular expression. It's a powerful tool for simple text parsing.
Imports System.Text.RegularExpressions
Namespace Demo
Class Program
Shared Function Main(ByVal args As String()) As Integer
Dim array As Integer() = ExtractIntegers("animals|3|2|1|3")
For Each i In array
Console.WriteLine(i)
Next
Return 0
End Function
Shared Function ExtractIntegers(ByVal input As String) As Integer()
Dim pattern As String = "animals(\|(?<number>[0-9]+))*"
Dim match As Match = Regex.Match(input, pattern)
Dim list As New List(Of Integer)
If match.Success Then
For Each capture As Capture In match.Groups("number").Captures
list.Add(Integer.Parse(capture.Value))
Next
End If
Return list.ToArray()
End Function
End Class
End Namespace
I haven't programmed VB for awhile but I'll give you some pseudo code:
First, loop through each line of file. Call this variable Line.
Then, take the index of what you're searching for: like Line.indexOf("animalsOwned")
If it returns -1 it isn't there; continue.
Once you find it, add the Index variable to the length of the search string and 1. (Index=Index+1+Len(searchString))
Then, take a substring starting there, and end at the end of the line.
Explode the substring by | characters, then add each into an array.
Return the array.
Sorry that I can't give you much help, but I'm working on an important PHP website right now ;).
You can do a variable.Split("|") and then assign each piece to an array level.
You can do a count on string and with a while or for loop, you can assign the splited sections to array levels. Then you can do a IsNumeric() check for each array level.

What is the best way to clear an array of strings?

What is the best way to clear an array of strings?
Wrong way:
myArray = Nothing
Only sets the variable pointing to the array to nothing, but doesn't actually clear the array. Any other variables pointing to the same array will still hold the value. Therefore it is necessary to clear out the array.
Correct Way
Array.Clear(myArray,0,myArray.Length)
And of course there's the VB way using the Erase keyword:
Dim arr() as String = {"a","b","c"}
Erase arr
Depending what you want:
Assign Nothing (null)
Assign a new (empty) array
Array.Clear
Last is likely to be slowest, but only option if you don't want a new array.
If you're needing to do things like clear, you probably want a collection like List(Of String) rather than an array.
redim arr(1,1,1,1)
and then
redim (z,x,y,v) to your dimensions
Here's a simple call that I use to clear the contents of a string array:
Public Sub ClearArray(ByRef StrArray As String())
For iK As Int16 = 0 To StrArray.Length - 1
StrArray(iK) = ""
Next
End Sub
Then just call it with:
ClearArray(myLocalArray)
If you need to reinitialize with empty strings or other values not equal to Nothing/Null, you may get further using an extension method:
Option Strict On : Option Explicit On : Option Infer On
...
Public Delegate Sub ArrayForAllDelegate(Of T)(ByRef message As T)
<Extension>
Public Function ForAll(Of T)(ByRef self As T(), f As ArrayForAllDelegate(Of T)) As T()
Dim i = 0
While i < self.Length
f(self(i))
i += 1
End While
Return self
End Function
Then your initialization code:
Dim a = New String(3 - 1) {"a", "b", "c"}
...
a.ForAll(Sub(ByRef el) el = "") 'reinitialize the array with actual empty strings
I use this code:
Redim myarray(-1)