Visual Basic 2D Array Issue - arrays

I currently am having some trouble trying to get my program to work with a 2D array. I had it working earlier with a 1D array but I am totally lost now that I have to make these changes.
Below is what I currently have as my 2D array and the code that I thought would work for spitting out a letter grade but does not give me anything. Would anyone be able to tell me what I'm doing wrong?
Private strGrades(,) As String = {{"900", "A"},
{"815", "B"},
{"750", "C"},
{"700", "D"},
{"0", "F"}}
Dim strGradeSearch As String
Dim intRow As Integer
strGradeSearch = txtGrade.Text
For intRow = 0 To 4
If intRow > strGrades.GetUpperBound(0) Then
strGrades(0, intRow) = strGradeSearch
intRow += 1
End If
Next intRow
If intRow <= strGrades.GetUpperBound(0) Then
lblLetter.Text = strGrades(intRow, 0)
End If

Please take all the following as positive comments :-)
OK. looking at your code, there are tbh several issues. You're trying to treat strings as numbers. While a string can contain what looks like a number, it only contains a string of characters that happen to be numbers. They make sense to use, but to a computer, they aren't. There is often stuff that VB does in the background to try and make life easier, but to be honest, it can be a pain.
When comparing something like grades, you need to compare actual numbers, not strings that contain numbers. You'll potentially get unexpected results. You need to get the computer to convert the string to a number. See below.
Your loop wont actually do anything because the If statement will never execute the code inside it because intRow will never be greater than the last element of the array. Anyhow.. Onwards.
A way to convert strings to numbers is to user the Val function, though this "old" VB. The current way is to use Integer.Parse. Have a look at this link for some basic information about it.
Lets walk through what you want to do.
Get the string in the textbox.
Convert the string to a number.
Loop through the array and for each element, get the number stored as a string and convert it to a number and then compare it to the grade number.
If the grade is greater than any of the values, make a note of the
letter linked to the grade and stop searching through the loop.
Assign the letter that was found to the label.
The following code should do this
Dim strGrades(,) As String = {{"900", "A"},
{"815", "B"},
{"750", "C"},
{"700", "D"},
{"0", "F"}}
Dim intGradeSearch As Integer
Dim strGradeLetter As String = ""
intGradeSearch = Integer.Parse(TxtGrade.Text)
For i As Integer = 0 To 4
If intGradeSearch >= Integer.Parse(strGrades(i, 0)) Then
strGradeLetter = strGrades(i, 1)
Exit For
End If
Next
LblLetter.Text = strGradeLetter
End Sub
You dont need to check intRow after the loop has finished, because in this case, at some point in the loop, a grade letter will always be found if the number in the textbox is greater than or equal to a number in the array.
If you have any questions, please don't hesitate to ask.

Related

Is there a way to transfer all values from one array to another, then erase the original array?

I'm running into a problem with a block of code I'm trying to develop at my job. Essentially, I'm creating a userform in excel where folks will enter data for railcars as they get loaded at a certain location (we'll call these "spot 1, spot 2, spot 3, etc.").
Sometimes they'll have to move that car to a different spot, in which case I want them to be able to keep all the information on the railcar from the first/original entry, and then erase the data from the original spot once that's done.
To accomplish this in a more streamlined fashion, I've established arrays for each of the 5 spots that reference all the cells they're entering data into on the userform:
Dim spot1information(14)
spot1information(0) = UserForm.ProductType1.Value
spot1information(1) = UserForm.ProductID1.Value
spot1information(2) = UserForm.BatchID1.Value
etc....
Dim spot2information(14)
spot2information(0) = UserForm.ProductType2.Value
spot2information(1) = UserForm.ProductID2.Value
spot2information(2) = UserForm.BatchID2.Value
etc....
And so forth for all five spots. I don't know if this makes things more difficult, but note that these array values aren't all of the same type. For instance, index (0) will be a string, but index (10) is a DATETIME and index (12) is defined as Long.
So say that they are moving a car from spot 1 to spot 2. In short, I want the code to do the following:
Replace the values of indices 0 - 6 in spot2information (which is currently empty) with the values of indices 0 - 6 in spot1information (which the user has filled on the userform).
I'm only interested in carrying over indices 0-6 because they contain the pertinent railcar information
Empty every value of spot1information to ""
To accomplish this, I tried the following code and a few variations thereof:
If OriginalSpot.Value = 1 Then
If DestinationSpot.Value = 2 Then
For i = 0 to 6
spot2information(i) = spot1information(i)
Next
For Each i in spot1information
spot1information(i) = ""
Next
End If
End If
However, this keeps coming up with a type mismatch. I figure because the data in the spot2information array is empty, and the data in the spot1information array is not, but I'm not entirely sure of a way around this.
Update: I did what was suggested below and replaced: spot1information(i) = "" with Erase spot1information
The code now essentially works! The values of array "spot2information" are now the former values of "spot1information", with "spot1information" now empty.
The 2D array suggested below also works like a charm. New problem I've been facing is that array values are updating, but the userform isn't. (note: in the future I'll be posting this type of thing as a separate question, my apologies!)
Easier to manage this as a 2D array:
Sub Tester()
Dim spots(1 To 5, 0 To 14), n As Long, i As Long
'fill your spot arrays from the form....
For n = 1 To 5
spots(n, 0) = UserForm.Controls("ProductType" & n).Value
spots(n, 1) = UserForm.Controls("ProductID" & n).Value
spots(n, 2) = UserForm.Controls("BatchID" & n).Value
'etc etc
Next n
'swap a spot with another
Debug.Print spots(2, 1), spots(3, 1)
SwapSpots spots:=spots, fromSpot:=2, toSpot:=3
Debug.Print spots(2, 1), spots(3, 1)
End Sub
Sub SwapSpots(spots, fromSpot As Long, toSpot As Long)
Dim n As Long
For n = 0 To 6
spots(toSpot, n) = spots(fromSpot, n)
spots(fromSpot, n) = Empty 'empty the source slot value
Next n
End Sub
Assuming the DataType of the arrays is the same by Index i.e. index(0) is string for all spots, Index(2) is long for all spots, and so on.
If that is the case then this part should not produce any error:
For i = 0 to 6
spot2information(i) = spot1information(i)
Next
The error should be happening in this part more precisely in the line marked with #
For Each i in spot1information
spot1information(i) = "" '#
Next
and the reason for the error it seems to be that trying to assign a string value "" to a numeric type, given the "mismatch" error.
Using For Each i in spot1information indicates that you want to "Initiate" or Erase the entire array, therefore I suggest to use this line instead of the For…Next method.
Erase spot1information
In regards this:
But I've now run into a new problem, where the values on the userform haven't updated to reflect the new values stored in the array. Do I need to somehow "refresh" the userform?
You just updated the arrays, then you need to run the procedures used to update the values of the objects affected by both arrays in the UserForm.

I'm unable to compare two lists in VB.Net

I have a project to make the dice game Yahtzee. Out of Five Dice, I have to sort the dice in ascending order, and then check if they are in a certain order. Thus the small straight score in Yahtzee. So for example, if I get a [1,2,3,4,6] Then I have a small straight. But if I had a [1,2,4,6,5], I would not.
This is my code(Dice list are the randomized list of dice, this scoring function determines if it matches the small straight criteria)
Dim Sorted_List() As Integer
Sorted_List(0) = Dice_List(0)
Sorted_List(1) = Dice_List(1)
Sorted_List(2) = Dice_List(2)
Sorted_List(3) = Dice_List(3)
Sorted_List(4) = Dice_List(4)
Array.Sort(Sorted_List)
Dim Fours_List(3) As Integer
Fours_List(0) = Sorted_List(0)
Fours_List(1) = Sorted_List(1)
Fours_List(2) = Sorted_List(2)
Fours_List(3) = Sorted_List(3)
Dim smlStr1() As Integer = {1, 2, 3, 4}
If (Fours_List Is smlStr1) Then
lblSmallStraight.Text = "55"
End If
So you've got an array called DiceList and it holds the result of rolling the dice. We can sort it pretty easily using LINQ:
Dim rolls = DiceList.OrderBy(Function(x) x)
But we should also remove duplicates as it makes the problem easier to deal with:
Dim rolls = DiceList.OrderBy(Function(x) x).Distinct().ToArray()
Now a simple way to look at it might be just to collect the differences of the rolls into a string:
Dim consec = ""
For i = 1 to Ubound(rolls)
sum += (rolls(i) - rolls(i-1)).ToString()
Next i
And then ask:
If consec.Contains("1111") Then 'its a large straight
Else If consec.Contains("111") Then 'there's a small straight
Of course, you might think it simpler to just list out some combos:
Dim smallStraights = {"1234","2345","3456"}
Dim largeStraights = {"12345","23456"}
Then turn your rolls into a string:
Dim rollsStr = string.Join("", rolls.Select(Function(x) x.ToString()))
And ask if the string contains any of the straights:
If largeStraights.Any(Function(ls) rollsStr.Contains(ls)) Then '...
Else If smallStraights.Any(Function(ss) rollsStr.Contains(ss)) Then '...
Note that this last syntax is a bit odd; because our straights are in an array and we're querying to see if the rolls string contains any of the straights, we can't start out with rollsStr.Contains(...).
Instead we actually need to ask "for all these straights in this array, is there any array element such that the rolls string contains the array element" ?
With loops it would look like:
'smallStraights.Any(Function(ss) rollsStr.Contains(ss))
For Each ss In smallStraights
If rollsStr.Contains(ss) Return True 'stop as soon as one is found
Next ss
Return False 'none found
So how does this all work?
We have a set of rolls:
{2, 1, 4, 3, 6}
As a human, we could look for consecutives in this either by
counting up and jumping back and forth (find the 1, go left to the 2, right three places to the 3, left to the 4, right to the 6, work out that we had 4 in a row, but not 5, call it a straight small)
rearranging the dice so they are shown in order and see how many of them are "one more than the one to their left"
The latter approach is what I start with - I sort the array and look at the differences. Because duplicates would ruin our approach (A set of 1,2,2,3,4 is actually a small straight if you throw one of the 2s away, but if you kept it you'd have a difference chain of 1,0,1,1 and the 0 would upset things if we were looking for a difference chain of 1,1,1) I also take them out as part of the "sort the dice" step
After we implemented the approach of sorting the dice and then going through them one by one working out the difference to the previous die, we built a string up that described the differences.
Building a string makes our life easier because there are built in methods that ask if one string contains another. If our difference string was e.g. "0111", or "1112" then it does contain a "111" which means a small straight is present (remember that there are 5 dice, but only 4 differences because the algorithm is "this_dice minus previous_dice" i.e. for 5 dice A,B,C,D,E we do B-A, C-B, D-C, and E-D - 4 differences for 5 dice)
Then we might realise that it's actually easier to not do the differneces thing, but just to order the dice, remove the duplicates and look for the small combination of dice that mean a straight is present.. This means we're literally taking our {1,2,3,4,6} rolls, turning them into a string of "12346" and then looking in it to see if we can find "1234", or "2345", or "3456" - the small straights. If we do this after we look for the big straights, and only if we didn't find a big straight, then for a roll set of "12345" we wouldn't accidentally declare it a small straight (because "12345" contains "1234") when its really a big straight
Why choose one over the other? Well, looking for the limited number of small/large straights (there are only 5) is viable because there are only 5. If Yahtzee had 100 sided dice, and a straight could be 1-2-3-4, 2-3-4-5, 3-4-5-6, 4-5-6-7, 5-6-7-8 .. all the way to 97-98-99-100 then it would make sense to do the differences method, because instead of listing 98 combinations of small straight, the differences method always reduces the variety of what we're looking for to "111"; a small straight of 1-2-3-4 or 97-98-99-100 both become 1-1-1-1 if we do differences
So all that remains is for your code to turn your list of numbers into a single string and then use Contains. This is a lot easier to do than ask "does this list of numbers contain this other list of numbers" so we (ab)use string into being our data container for the numbers because it means they're no longer separate things that have to be cross coordinated; they're a single string that contains a pattern and over the time we've developed lots of ways in programming languages, of looking for patterns inside strings
You certainly could have one bunch of numbers, {1,2,3,4,6} and ask "does this set of numbers contain this other set of numbers {1,2,3,4}" but it would look a bit more like this (in programming 101 terms, not using LINQ or Sets etc)
Dim rolls = {1,2,3,4,6} 'note: these values must be unique
Dim straight = {1,2,3,4} 'note: these values must be unique
Dim numFound = 0
For Each r in rolls
For Each s in straight
If r == s Then
numFound += 1
End If
Next s
Next r
If numFound = straight.Length Then 'if we found all the numbers in the straight
Console.Write("All the numbers of the straight exist in the rolls")
End If
We still have the problem that duplicates defeat this method so we need to dedupe our rolls. We could do that by adding a bit on:
Dim numFound = 0
Dim prevR = -1
For Each r in rolls
If r = prevR Then Continue 'skip this one, it's a duplicate of the previous roll
prevR = r ' remember the current roll for next time
For Each s in straight
If r == s Then
numFound += 1
End If
Next s
Next r
We also still have the problem that the rolls need to be sorted, because we only check the previous one roll. If we are going to be working with unsorted rolls, then we need to check all the previous ones to see if the current roll occurred already:
For i = LBound(rolls) to UBound(rolls)
Dim r = rolls(i)
'Check ALL the previous rolls
Dim seenBefore = False
For p = i - 1 To LBound(rolls) Step -1
Dim prevR = rolls(p)
If prevR = r Then seenBefore = True
Next p
If seenBefore Then Continue 'skip this one, it's a duplicate of a previous roll
For Each s in straight
If r == s Then
numFound += 1
End If
Next s
Next r
You can see how the problem starts growing every time we think about it/try to solve another problem/bug with the previous iteration. All in we now have a mechanism for checking if one set of numbers exists in another set of numbers but its quite lengthy compared to our previous steps:
Dim rolls = {1,3,2,5,4}
Dim rollsString = string.Join("", rolls.OrderBy(...).Distinct()) 'turn the rolls into a string like "12345"
If rollsString.Contains("1234") Then 'rolls contains the small straight 1234
Note, these use LINQ extensively and you might not have been taught LINQ.. You might need an approach that implements the algorithm "sort, unique, look for straights" using what you know already, but equally you can probably do your own learning (getting help learning on SO is possible, though a lot of people want to just give you the answer in the shortest time possible to try and win the points) and justify your algorithm and your solution to the teacher.
If you don't want to use LINQ, you can take a look at some of the other things discussed and assemble a solution. Deduping a List can be done by something like:
Dim uniqueList as New List(Of Integer) 'must be a list, not an array
For Each i in listWithDuplicates
If Not uniqueList.Contains(i) Then uniqueList.Add(i)
Next i

how to sort an array of string containing numbers, numerically

So as the title already suggests I simply want to know if it is possible to sort elements that are in a string array via the numbers that are also contained within those strings.
In my program I have been able to read the contents of a file which are laid out like this:
Adele got 2,Jack got 8
I have then been able to split this file via the comma into a string array called names:
Dim fileReader As System.IO.StreamReader
Dim line As String
fileReader = My.Computer.FileSystem.OpenTextFileReader(ClassA)
line = fileReader.ReadLine
names = line.Split(",")
For Each element As String In names
Array.Sort(names)
Console.WriteLine(element)
Next
I was also able to sort the file alphabetically giving an output of:
Adele got 2
Jack got 8
However what I want to know is whether it is or isn't possible to sort these strings based on the number so the output would look like:
Jack got 8
Adele got 2
I also considered using regex to extract the numbers, parse them as integer, save them to a variable and then add them to an array and compare the arrays but their has to be a simpler way D:
Any help is much appreciated guys :)
Linq to the rescue. I added to the data to be sure it would handle 2 digit numerals and because 2 elements is a feeble test:
Dim phrases As String() = {"Adele got 2", "Zalgo got 9", "Jack got 8", "Ziggy got 11"}
phrases = phrases.OrderByDescending(Function(q) Int32.Parse(q.Split(" ").Last)).ToArray()
For Each s In phrases
Console.WriteLine(s)
Next
Result:
Ziggy got 11
Zalgo got 9
Adele got 8
Jack got 2
Yes, it's possible. Add a comparer function, and then sort using it. This is the comparer:
Private Shared Function WhoGotComparer(ByVal x As String, ByVal y As String) As Integer
Dim parseX() As String = Split(x, " got ")
Dim parseY() As String = Split(y, " got ")
If parseX.Length <> 2 Or parseY.Length <> 2 Then Return 0
Return CInt(parseY(1)).CompareTo(CInt(parseX(1))) 'Performs an integer comparison.
End Function
Add it to your code. You can make use of it like this:
names.Sort(AddressOf WhoGotComparer)
This should result in the array being sorted in descending order. I think that is what you wanted based on the example. If you want to change the order to ascending you can reverse the roles of they x and y parameters in the comparer function, or you can negate the results of the Compare.

Index was outside the bounds of the String array

I have this test code in VB:
Dim st As New ScheduledTasks("\\webserver")
Dim tasknames() As String = st.GetTaskNames
ListBox1.Items.Add(tasknames(1))
st.Dispose()
When i run it i get an error on line:
ListBox1.Items.Add(tasknames(1))
Index was outside the bounds of the array.
Does anyone have any suggestions what im doing wrong?
tasknames must contain at least 2 items for your code to work. (Note that arrays are 0-based, so you start counting at 0)
You have to check whether it really contains that amount:
If tasknames.Length >= 2
Then
ListBox1.Items.Add(tasknames(1))
End If

Largest word in an Array

Hey i'm having problems creating a simple button for a programme which finds the largest word in an array and puts it into a textbox. I've done most of the coding (I hope) was wondering if somebody could help me actually with the code that finds the largest text in the array as I am struggling the most with that.
Private Sub btnLongName_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLongName.Click
Dim LongName As String
Dim LengthOfLongestName As Integer
Dim UltimateName As String
For i As Integer = 0 To 5
LongName = Members(i).Name
LengthOfLongestName = Len(LongName)
If Members(i).Name.Length > LengthOfLongestName Then
End If
Next i
txtResult.Text = "The longest name is " & UltimateName & " ."
End Sub
End Class
Thanks for your time - its for college homework, struggling big time on it :(
edit: I've edited the code
Since this is homework, I won't write the code for you; instead I'll try to give you some hints that will point you in the right direction.
Declare a variable of an appropriate type to hold the <longest value so far>, initialize it with the "shortest" value for that type.
Loop through all the values in the array (perhaps with a For or For Each loop)
Pseudo-code for the inside your loop:
If the Length of <the value being checked> exceeds _
the Length of the <longest value so far> Then
Assign <the value being checked> to the <longest value so far>
End If
When the loop finishes, the <longest value so far> will be the longest value in the array.
Notes
You can use MSDN as a reference on how to use a For loop or a For Each loop (If you haven't learned For loops yet, you can also use a Do Loop)
<the value being checked> will be different on each iteration through the loop; it should correspond to each consecutive value in your array. You can verify that this is working by setting a breakpoint.
You can get the length of a string by saying myString.Length
If you've learned about Functions, consider writing a function that takes an array as a parameter, and returns the longest value in the array.
There are certainly ways you could do this with LINQ, but I don't think that is the goal of the assignment ;-]
In response to Edit 1:
Your If statement needs to be inside of some sort of loop (For, For Each, Do, etc) I think this is the key concept that you are missing.
Instead of comparing LongName.Length to LengthOfLongestName, you need to compare the length of an entry in your array to LengthOfLongestName
You're on the right track with Members(0).Name.Length, but you can't just check element 0; you have to check every element in the array.
Given your current code, you'll probably be assigning <An entry in your array>.Name to LongName
The last index in a one-dimensional array is <array>.Length - 1 or <array>.GetUpperBound(0).
The following doesn't really address anything in your assignment, but I hope it will give you some ideas on how to go through all the items in your list:
' A For loop that does a message box for each of the numbers from 0 to 5 '
For i as Integer = 0 To 5
MessageBox.Show(i)
Next i
' Code that does a message box with the names of the 2nd, 3rd and last '
' entries in Members '
' (Remember that the first item is at 0, the second item is at 1, etc...) '
MessageBox.Show(Members(1).Name)
MessageBox.Show(Members(2).Name)
MessageBox.Show(Members(Members.GetUpperBound()).Name)
In response to Edit 2:
You're getting warmer...
You should only update LongName and LengthOfLongName if the current value is the longest you've seen so far (i.e. they should be assigned inside of the If statement)
You have to go to the last index of the array, not 5. See above (the response to your first edit) on how to get that last index.
You don't really need the UltimateName variable; you can just use LongName ;-]
You might want to use <stringVariable>.Length instead of Len(<stringVariable>) to be consistent.
What you are missing is a loop that checks each member, and putting the If statement inside it and make it compare the length of the name to the longest name that you have found so far. If the name is longer, put it in the variable for the longest found, and update the length variable.
You can either initialise the variables with the name of the first member and loop from the second member and on, or you can initialise the variables with an empty string and loop all the members. Personally I prefer the latter one.

Resources