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
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.
I'm trying to create a vector in VBA using a for loop. My problem is that VBA doesn't allow me to have a different equation for the first vector coordinate. When trying to run it I get "expected array" as an error message
'radius calculations
r(1) = (al * Log(al) / (al - 1)) * rb ' middle radius of block 1, trying to calculate first entry
'in r-vector, since this equation is different from the rest
r_m(1) = rb 'r_i-1/2 i=1
For i = 2 To n_r
r(i) = al * r(i - 1) ' r_i
r_m(i) = (r(i) - r(i - 1)) / (Log(r(i) / r(i - 1)))
Next i
al and rb is defined as Double (Public Const), while r is defined as a string. I have only used Matlab in the past, and only read intro guides to VBA (VBA for Dummies etc.)
Greatly appreciate all help in the matter
E
VBA doesn't treat strings the same way it treats arrays (like you'd see in other languages). r(1) looks like array syntax to VBA (as can be seen here); therefore, it's going to error out if it's declared as a string.
So, essentially, VBA is interpreting r(1) to mean, "I have an array named r and I want to store something into element number 1". But, instead, it's attempting to do this to an immutable string.
The Split function can be used to turn a string into an array, if that's what you need.
Still, it'll be best if you straight-up declare an array for your vector math.
Dim myArray() As Double
Dim myArray2(10, 10, 10) As Double
The ReDim keyword can be used to resize an array, even within a for loop. (Just don't forget to ReDim Preserve if you need to make sure that the contents don't get wiped during this operation).
I declare my array
Dim A(N) As Integer
When I loop from 1 To N or 0 To N-1 there's an extra value at one end or the other.
What's going on?
(Intended to be a canonical question/answer.)
In VB.NET arrays almost always* have a lower bound of 0 and are declared mentioning their upper bound, not their length.
They did change the VB.NET syntax early on to allow you to remind yourself if needed:
Dim A(0 To N) As Integer
That 0 can't be anything else (such as a 1 or a constant zero).
You can loop through all VB.NET array indexes using
For i = LBound(A) To UBound(A)
or, more simply,
For i = 0 To N
(*) You can use the .NET Framework to create arrays with other lower bounds, but you need to refer to them as Array and thus with late binding (and probably Option Strict Off).
So I have been busy working on an assignment lately, now I'm programming in Visual Basic, Using Visual Studio 2013 Update 4, and I'm working in the .NET Framework.
Here's my problem/question:
What I want to accomplish is that I run through an array of 81 (length) consisting of only characters. I want to iterate through this array and after 9 steps of my for-loop I want to save those 9 characters into a string.
With this I mean I want to save characters 0-8 in a string, then 9-17 in another string, and so on…
(Array will be filled in my program)
Dim charactersArray(81) as character
For intIndex as integer = 0 to 81
'Add 9 characters into a string
Next
I have tried a-lot to accomplish this but have failed to find a solution yet, I have searched all over the internet but I couldn't find a solution.
So hopefully anybody here can help me out. :D
(Pretty much I'm asking you to make a little algorithm for me :/ )
This prints to console instead of adding items to something like an ArrayList, it also fixes the off-by-one bug in the original For Loop:
Dim charactersArray(81) as character
For intIndex as integer = 0 to 80 Step 9
Dim s = New String(charactersArray, intIndex, 9)
System.Console.WriteLine(s)
Next
There are three possibilities that comes to mind.
The first one is a classical approach that builds a string one character at time until you have read 9 characters, then restart for the next 9 character until you reach the end of the array.
The trick here is the MOD operator to discover when you have read 9 characters (Notice that I start from one to avoid the first 0 MOD 9 that returns 0)
Dim sb = new StringBuilder()
For intIndex as integer = 1 to 81
if intIndex Mod 9 = 0 then
sb.Append(charactersArray(intIndex - 1))
Console.WriteLine(sb.ToString())
sb.Clear()
else
sb.Append(charactersArray(intIndex - 1))
End if
Next
The other approach uses Linq and is a lot more readable
Dim index = 0
Do while(index < 81)
Dim s = new String(charactersArray.Skip(index).Take(9).ToArray())
Console.WriteLine(s)
index += 9
Loop
Here I build a new string skipping the characters already read and build a new string using the next 9 characters
A third approach is using the Array.Copy method
Dim charBuffer(8) as Char
Dim index = 0
Do while(index < 81)
Array.Copy(charactersArray, index, charBuffer, 0, 9)
Console.WriteLine(new string(charBuffer))
index += 9
Loop
The performance of the three methods in a loop of 100000 iterations are the following (take it with a lot of prudence and test on your hardware)
StringBuilder:66 ms
Linq:706 ms
Array.Copy:40 ms