Loop Through List And Keep Index Count VB.net - vb.net

How do I irate through a list/array in vb.net and also keep the count of the current item I'm on without having to declare an explicit counter variable?
The result that I am trying to achieve is as follows.
dim i as integer = 0 'evil counter variable
dim arr() as string = {"a","b","c","d","e"}
for each item in arr
console.writeline("Item """ & item & """ is index " & i)
i+=1
next
Without having to declare "i" on its own line
Python has a shorthand way of doing this as follows.
arr=["a","b","c","d","e"]
for i, item in enumerate(arr):
print("Item """ + item + """ is index " + str(i))
A similar implementation in vb.net would be ideal.
edit
The significant part of the code is the enumeration. Not the printing of the values.

Dim arr() As String = {"a", "b", "c", "d", "e"}
For i = 0 To arr.GetUpperBound(0)
Debug.Print($"Item {arr(i)} is index {i}")
Next

The list of possible solutions would not be complete without a Reflection hack, so let's go old school and use an enumerator
Dim arr() As String = {"a", "b", "c", "d", "e"}
Dim enumer As IEnumerator = arr.GetEnumerator
Dim fi As Reflection.FieldInfo = enumer.GetType.GetField("_index", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)
While enumer.MoveNext
Dim item As String = DirectCast(enumer.Current, String)
Console.WriteLine($"item: {item} is at index: {fi.GetValue(enumer)}")
End While
A little explanation is needed. arr.GetEnumerator returns a SZArrayEnumerator. This enumerator uses the private field _index to keep track of the array index of the item returned by the Enumerator. The code uses reflection to access the value of _index.
Since this is not a generic Enumerator, the type returned by the Current property is System.Object and needs to be cast back to a String.

As an alternative, you could use string.Join() on a Linq Select():
Console.Write(String.Join(vbCrLf, arr.Select(Function(s, i) "Item " & s & " is Index " & i)))

It's not as good as the Python way, but this will do as you require:
dim arr() as string = {"a","b","c","d","e"}
for i as integer = 0 to arr.length - 1
console.writeline("Item """ & arr(i) & """ is index " & i)
next
Again, not as clean but it does as you need :)

Related

How to replace multiple similar values with a single value

Public Function SameStuff(s1 As String, s2 As String) As Boolean
Dim bad As Boolean
SameStuff = False
ary1 = Split(Replace(s1, " ", ""), ",")
ary2 = Split(Replace(s2, " ", ""), ",")
Length1 = UBound (ary1)
Length2 = UBound(ary2)
k=1
If Length1<= Length2 and Length1<>0 then
for i=0 to Length1-1
If ary1(i) = ary2(i) then
ary3(k,i) = ary1(i)
End If
Next i
k=k+1
else
Exit function
End If
End Function
Here I take value from Range("A1") - (has 3 words) and value of Range("A2") - (has 4 words). Split them both by finding space between words and store them in arrays. If length of one array is 3 and other is 4, 3 words from both the arrays will be compared. If 3 words are found to be same then Range("B1") and Range("B2") must both have the 3 word name i.e Range("A1").Value. I think this logic will work fine to find similar names like "ABC DEF HIJ " in A1 and "ABC DEF HIJ Limited" in A2.
I am not able to put it in code.
Word length will not remain the same i.e 3,4 .
Use a dictionary would be an easy alternative, you can use the .exists method to do this for you, you have to transfer the array (result of split() ) to a dictionary tho, but that's a loop, not too tricky. Or, you could leave one of the inputas as a string and split only 1, and use if strStringLeftAlone like "* " & strSection(x) & " *" or use instr, with the same idea as the search " " & strSection(x) & " " or find
This should work regardless how long the arrays are, i.e. no matter how many words (and spaces) there are in each of the strings to be compared. Notice I removed the k variable as it didn't seem to serve any purpose in the code. This solution does presuppose, however, that ONLY the LAST word in the two strings is different.
Public Function SameStuff(s1 As String, s2 As String) As Boolean
Dim sameBool As Boolean
Dim i As Long, Length1 As Long, Length2 As Long
Dim tempArr1 as String, tempArr2 as String
Dim ary1 as Variant, ary2 as Variant
ary1 = Split(Replace(s1, " ", ""), ",")
ary2 = Split(Replace(s2, " ", ""), ",")
Length1 = UBound (ary1)
Length2 = UBound(ary2)
If Length1 <= Length2 and Length1 > 0 then
For i=0 to Length1-1
tempArr1 = tempArr1 & ary1(i)
tempArr2 = tempArr2 & ary2(i)
Next i
If tempArr1 = tempArr2 then sameBool = True
End If
SameStuff = sameBool
End Function
Edit
Added some variable declarations to the code that I had forgotten, otherwise the code would not work with Option Explicit at the top of the module.

VBA string interpolation syntax

What is the VBA string interpolation syntax? Does it exist?
I would to to use Excel VBA to format a string.
I have a variable foo that I want to put in a string for a range.
Dim row as Long
row = 1
myString = "$row:$row"
I would like the $row in the string to be interpolated as "1"
You could also build a custom Format function.
Public Function Format(ParamArray arr() As Variant) As String
Dim i As Long
Dim temp As String
temp = CStr(arr(0))
For i = 1 To UBound(arr)
temp = Replace(temp, "{" & i - 1 & "}", CStr(arr(i)))
Next
Format = temp
End Function
The usage is similar to C# except that you can't directly reference variables in the string. E.g. Format("This will {not} work") but Format("This {0} work", "will").
Public Sub Test()
Dim s As String
s = "Hello"
Debug.Print Format("{0}, {1}!", s, "World")
End Sub
Prints out Hello, World! to the Immediate Window.
This works well enough, I believe.
Dim row as Long
Dim s as String
row = 1
s = "$" & row & ":$" & row
Unless you want something similar to Python's or C#'s {} notation, this is the standard way of doing it.
Using Key\Value Pairs
Another alternative to mimic String interpolation is to pass in key\value pairs as a ParamArray and replace the keys accordingly.
One note is that an error should be raised if there are not an even number of elements.
' Returns a string that replaced special keys with its associated pair value.
Public Function Inject(ByVal source As String, ParamArray keyValuePairs() As Variant) As String
If (UBound(keyValuePairs) - LBound(keyValuePairs) + 1) Mod 2 <> 0 Then
Err.Raise 5, "Inject", "Invalid parameters: expecting key/value pairs, but received an odd number of arguments."
End If
Inject = source
' Replace {key} with the pairing value.
Dim index As Long
For index = LBound(keyValuePairs) To UBound(keyValuePairs) Step 2
Inject = Replace(Inject, "{" & keyValuePairs(index) & "}", keyValuePairs(index + 1), , , vbTextCompare)
Next index
End Function
Simple Example
Here is a simple example that shows how to implement it.
Private Sub testingInject()
Const name As String = "Robert"
Const age As String = 31
Debug.Print Inject("Hello, {name}! You are {age} years old!", "name", name, "age", age)
'~> Hello, Robert! You are 31 years old!
End Sub
Although this may add a few extra strings, in my opinion, this makes it much easier to read long strings.
See the same simple example using concatenation:
Debug.Print "Hello, " & name & "! You are " & age & " years old!"
Using Scripting.Dicitionary
Really, a Scripting.Dictionary would be perfect for this since they are nothing but key/value pairs. It would be a simple adjustment to my code above, just take in a Dictionary as the parameter and make sure the keys match.
Public Function Inject(ByVal source As String, ByVal data As Scripting.Dictionary) As String
Inject = source
Dim key As Variant
For Each key In data.Keys
Inject = Replace(Inject, "{" & key & "}", data(key))
Next key
End Function
Dictionary example
And the example of using it for dictionaries:
Private Sub testingInject()
Dim person As New Scripting.Dictionary
person("name") = "Robert"
person("age") = 31
Debug.Print Inject("Hello, {name}! You are {age} years old!", person)
'~> Hello, Robert! You are 31 years old!
End Sub
Additional Considerations
Collections sound like they would be nice as well, but there is no way of accessing the keys. It would probably get messier that way.
If using the Dictionary method you might create a simple factory function for easily creating Dictionaries. You can find an example of that on my Github Library Page.
To mimic function overloading to give you all the different ways you could create a main Inject function and run a select statement within that.
Here is all the code needed to do that if need be:
Public Function Inject(ByVal source As String, ParamArray data() As Variant) As String
Dim firstElement As Variant
assign firstElement, data(LBound(data))
Inject = InjectCharacters(source)
Select Case True
Case TypeName(firstElement) = "Dictionary"
Inject = InjectDictionary(Inject, firstElement)
Case InStr(source, "{0}") > 0
Inject = injectIndexes(Inject, CVar(data))
Case (UBound(data) - LBound(data) + 1) Mod 2 = 0
Inject = InjectKeyValuePairs(Inject, CVar(data))
Case Else
Err.Raise 5, "Inject", "Invalid parameters: expecting key/value pairs or Dictionary or an {0} element."
End Select
End Function
Private Function injectIndexes(ByVal source As String, ByVal data As Variant)
injectIndexes = source
Dim index As Long
For index = LBound(data) To UBound(data)
injectIndexes = Replace(injectIndexes, "{" & index & "}", data(index))
Next index
End Function
Private Function InjectKeyValuePairs(ByVal source As String, ByVal keyValuePairs As Variant)
InjectKeyValuePairs = source
Dim index As Long
For index = LBound(keyValuePairs) To UBound(keyValuePairs) Step 2
InjectKeyValuePairs = Replace(InjectKeyValuePairs, "{" & keyValuePairs(index) & "}", keyValuePairs(index + 1))
Next index
End Function
Private Function InjectDictionary(ByVal source As String, ByVal data As Scripting.Dictionary) As String
InjectDictionary = source
Dim key As Variant
For Each key In data.Keys
InjectDictionary = Replace(InjectDictionary, "{" & key & "}", data(key))
Next key
End Function
' QUICK TOOL TO EITHER SET OR LET DEPENDING ON IF ELEMENT IS AN OBJECT
Private Function assign(ByRef variable As Variant, ByVal value As Variant)
If IsObject(value) Then
Set variable = value
Else
Let variable = value
End If
End Function
End Function
Private Function InjectCharacters(ByVal source As String) As String
InjectCharacters = source
Dim keyValuePairs As Variant
keyValuePairs = Array("n", vbNewLine, "t", vbTab, "r", vbCr, "f", vbLf)
If (UBound(keyValuePairs) - LBound(keyValuePairs) + 1) Mod 2 <> 0 Then
Err.Raise 5, "Inject", "Invalid variable: expecting key/value pairs, but received an odd number of arguments."
End If
Dim RegEx As Object
Set RegEx = CreateObject("VBScript.RegExp")
RegEx.Global = True
' Replace is ran twice since it is possible for back to back patterns.
Dim index As Long
For index = LBound(keyValuePairs) To UBound(keyValuePairs) Step 2
RegEx.Pattern = "((?:^|[^\\])(?:\\{2})*)(?:\\" & keyValuePairs(index) & ")+"
InjectCharacters = RegEx.Replace(InjectCharacters, "$1" & keyValuePairs(index + 1))
InjectCharacters = RegEx.Replace(InjectCharacters, "$1" & keyValuePairs(index + 1))
Next index
End Function
I have a library function SPrintF() which should do what you need.
It replaces occurrences of %s in the supplied string with an extensible number of parameters, using VBA's ParamArray() feature.
Usage:
SPrintF("%s:%s", 1, 1) => "1:1"
SPrintF("Property %s added at %s on %s", "88 High St, Clapham", Time, Date) => ""Property 88 High St, Clapham added at 11:30:27 on 25/07/2019"
Function SprintF(strInput As String, ParamArray varSubstitutions() As Variant) As String
'Formatted string print: replaces all occurrences of %s in input with substitutions
Dim i As Long
Dim s As String
s = strInput
For i = 0 To UBound(varSubstitutions)
s = Replace(s, "%s", varSubstitutions(i), , 1)
Next
SprintF = s
End Function
Just to add as a footnote, the idea for this was inspired by the C language printf function.
I use a similar code to that of #natancodes except that I use regex to replace the occurances and allow the user to specifiy description for the placeholders. This is useful when you have a big table (like in Access) with many strings or translations so that you still know what each number means.
Function Format(ByVal Source As String, ParamArray Replacements() As Variant) As String
Dim Replacement As Variant
Dim i As Long
For i = 0 To UBound(Replacements)
Dim rx As New RegExp
With rx
.Pattern = "{" & i & "(?::(.+?))?}"
.IgnoreCase = True
.Global = True
End With
Select Case VarType(Replacements(i))
Case vbObject
If Replacements(i) Is Nothing Then
Dim Matches As MatchCollection
Set Matches = rx.Execute(Source)
If Matches.Count = 1 Then
Dim Items As SubMatches: Set Items = Matches(0).SubMatches
Dim Default As String: Default = Items(0)
Source = rx.Replace(Source, Default)
End If
End If
Case vbString
Source = rx.Replace(Source, CStr(Replacements(i)))
End Select
Next
Format = Source
End Function
Sub TestFormat()
Debug.Print Format("{0:Hi}, {1:space}!", Nothing, "World")
End Sub

How to prepend to a StringBuilder?

VB.NET have a method, just like java, to append to an object of type StringBuilder
But can I prepend to this object (I mean a add some string before the stringbuilder value, not after). Here is my code:
'Declare an array
Dim IntegerList() = {24, 12, 34, 42}
Dim ArrayBefore As New StringBuilder()
Dim ArrayAfterRedim As New StringBuilder()
ArrayBefore.Append("{")
For i As Integer = IntegerList.GetLowerBound(0) To IntegerList.GetUpperBound(0)
ArrayBefore.Append(IntegerList(i) & ", ")
Next
' Close the string
ArrayBefore.Append("}")
'Redimension the array (increasing the size by one to five elements)
'ReDim IntegerList(4)
'Redimension the array and preserve its contents
ReDim Preserve IntegerList(4)
' print the new redimesioned array
ArrayAfterRedim.Append("{")
For i As Integer = IntegerList.GetLowerBound(0) To IntegerList.GetUpperBound(0)
ArrayAfterRedim.Append(IntegerList(i) & ", ")
Next
' Close the string
ArrayAfterRedim.Append("}")
' Display the two arrays
lstRandomList.Items.Add("The array before: ")
lstRandomList.Items.Add(ArrayBefore)
lstRandomList.Items.Add("The array after: ")
lstRandomList.Items.Add(ArrayAfterRedim)
If you look at the last 4 lines of my code, I want to add the text just before the string builder all in one line in my list box control. So instead of this:
lstRandomList.Items.Add("The array before: ")
lstRandomList.Items.Add(ArrayBefore)
I want to have something like this:
lstRandomList.Items.Add("The array before: " & ArrayBefore)
You can use StringBuilder.Insert to prepend to the string builder:
Dim sb = New StringBuilder()
sb.Append("World")
sb.Insert(0, "Hello, ")
Console.WriteLine(sb.ToString())
This outputs:
Hello, World
EDIT
Oops, noticed #dbasnett said the same in a comment...
Your code seems like a lot of overkill to use StringBuilder with those For loops.
Why not do this?
Dim IntegerList() = {24, 12, 34, 42}
lstRandomList.Items.Add("The array before: ")
lstRandomList.Items.Add(String.Format("{{{0}}}", String.Join(", ", IntegerList)))
ReDim Preserve IntegerList(4)
lstRandomList.Items.Add("The array after: ")
lstRandomList.Items.Add(String.Format("{{{0}}}", String.Join(", ", IntegerList)))
Job done. Much simpler code.

How to reverse "This is friday" to "friday is this" in vb.net in Easiest Way

'How to reverse "This is Friday" to "Friday is this" in vb.net in Easiest Way
Dim str As String = txtremarks.Text
Dim arr As New List(Of Char)
arr.AddRange(str.ToCharArray)
arr.Reverse()
Dim a As String = ""
For Each l As Char In arr
a &= l
Next
' I saw on a few forums that to use SPLIT function. Please help
Yes you can use split. You can also use join and the reverse method:
Dim test = "This is Friday"
Dim reversetest = String.Join(" ", test.Split().Reverse)
First you'll want to split your sentence into individual words. This is where you'd use the String.Split method.
Once you have an array containing your individual words, you can reverse that array. Perhaps using Linq's Enumerable.Reverse extension method.
Finally, you can put the words back together into a string. The String.Join method allows you to join the elements of a string array back into a single string.
I'm not a VB programmer, but something like this should work:
Dim str As String = "this is friday"
Dim split As String() = str.Split(" ")
Dim result as String = String.Join(" ", split.Reverse())
Here's a way to do it in 1 line:
Dim reverse As String = "This is friday".Split().Reverse().Aggregate(Function(left, right) String.Join(" ", left, right))
Do note that this has a horrible performance overhead.
Yes, you can Split your String via " " (space) and insert results into array.
Next, read array from the end to start.
Good luck!
Try this...
Dim txt As String = "This is friday"
Dim txtarray() As String = Split(txt.Trim(), " ")
Dim result As String = ""
For x = txtarray.GetUpperBound(0) To 0 Step -1
result += txtarray(x) & " "
Next x
MsgBox(result.Trim())

A better way of writing this : growing array

Was looking at some code earlier, and am thinking that there has to be a more elegant way of writing this....
(returnVar.Warnings is a string array, it could be returned as any size depending on the number of warnings that are logged)
For Each item In items
If o.ImageContent.ImageId = 0 Then
ReDim Preserve returnVar.Warnings(returnVar.Warnings.GetUpperBound(0) + 1)
returnVar.Warnings(returnVar.Warnings.GetUpperBound(0)) = "Section: " & section.<header>.<title>.ToString & " , Item: " & item.<title>.ToString
End If
Next
use the generic List(of string) then get an array containing the list data if you need it
dim list = new List(of string)
list.Add("foo")
list.Add("bar")
list.ToArray()
Can't you use ArrayList which does this for you?
http://msdn.microsoft.com/en-us/library/system.collections.arraylist.aspx
Start by moving the If statement out of the loop.
If you are using framework 3.5, you can use LINQ to loop the items.
If o.ImageContent.ImageId = 0 Then
returnVar.Warnings = items.Select(Function(item) "Section: " & section.<header>.<title>.ToString & " , Item: " & item.<title>.ToString).ToArray()
Else
returnVar.Warnings = New String() {}
End If