How to not generate a stack overflow when a sub procedure calls itself? - vb.net

This code generates a stack overflow. I'm aware it is caused by the procedure calling itself.
What can I do to avoid the stack overflow? Recalling the sub procedure and generating a new random number is the easiest thing to do, however it generates the overflow. The randomly generated number picks a random inventory item, then the if statement matches that number (random inventory item) with the quantity of that item from the deck inventory to make sure it isn't less than 1. If the inventory of that item is 0, the else plays and restarts the procedure, generating a new random number and doing the process all over again. In another procedure I have a function that if the deck's inventory becomes completely empty, then the discard pile replenishes the deck, making the discard pile empty, so there should never be a case where all randomly generated numbers can be associated item with a inventory of 0.
I wonder if I could somehow force the random number generator
Number = (DeckGroup(Rnd.Next(0, DeckGroup.Count)).ID)
not to generate numbers to inventory items DeckGroup(Number).QuantityInteger that are zero. By doing so I wouldn't even need to recall the function.
The random number is generated by a different branch in the same structure group.
Private Sub PlayElse()
Dim CardCheckBoxArray() As CheckBox = {CardCheckBox1, CardCheckBox2, CardCheckBox3, CardCheckBox4, CardCheckBox5}
'Reset Number Generator
Number = (DeckGroup(Rnd.Next(0, DeckGroup.Count)).ID)
Dim PlayerQuantitySubtractionInteger As Integer
For PlayerQuantitySubtractionInteger = ChecksDynamicA To ChecksDynamicB
If CardCheckBoxArray(TextBoxInteger).Checked = True And DeckGroup(Number).QuantityInteger > 0 Then
DeckGroup(Number).QuantityInteger -= 1
'Select the Player depending value of T
Select Case T
Case 0
Player1HandGroup(Number).QuantityInteger += 1
Case 1
Player1HandGroup(Number).QuantityInteger2 += 1
Case 2
Player1HandGroup(Number).QuantityInteger3 += 1
Case 3
Player1HandGroup(Number).QuantityInteger4 += 1
Case 4
Player1HandGroup(Number).QuantityInteger5 += 1
End Select
CardTypeArray(PlayerQuantitySubtractionInteger) = Player1HandGroup(Number).CardType
CardCheckBoxArray(TextBoxInteger).Text = Player1HandGroup(Number).CardNameString
NumberArray(PlayerQuantitySubtractionInteger) = Number
Else
If CardCheckBoxArray(TextBoxInteger).Checked = True And DeckGroup(Number).QuantityInteger < 0 Then
Call PlayElse()
End If
End If
Next PlayerQuantitySubtractionInteger
End Sub

You could use LINQ to weed out all the objects you never want to get first and then use the collection returned by the linq instead of your original collection.
Something like:
Private Sub PlayElse()
Dim CardCheckBoxArray() As CheckBox = {CardCheckBox1, CardCheckBox2, CardCheckBox3, CardCheckBox4, CardCheckBox5}
'Reset Number Generator
Dim temp As IEnumerable(Of LunchMoneyGame.LunchMoneyMainForm.Group) = From r In DeckGroup Where r.QuantityInteger > 0 Select r
If temp IsNot Nothing AndAlso temp.Any Then
Number = (temp(Rnd.Next(0, temp.Count)).ID)
' ** Edit **: This will ensure that you only got 1 object back from the LINQ which can tell you whether or not you have bad data. You *can* exclude this check but its good practice to include it.
Dim obj As LunchMoneyGame.LunchMoneyMainForm.Group = Nothing
Dim t = From r In temp Where r.ID = Number Select r
If t IsNot Nothing AndAlso t.Count = 1 Then
obj = t(0)
End If
If obj IsNot Nothing Then
Dim PlayerQuantitySubtractionInteger As Integer
For PlayerQuantitySubtractionInteger = ChecksDynamicA To ChecksDynamicB
' ** Edit **
obj.QuantityInteger -= 1
'Select the Player depending value of T
Select Case T
Case 0
Player1HandGroup(Number).QuantityInteger += 1
Case 1
Player1HandGroup(Number).QuantityInteger2 += 1
Case 2
Player1HandGroup(Number).QuantityInteger3 += 1
Case 3
Player1HandGroup(Number).QuantityInteger4 += 1
Case 4
Player1HandGroup(Number).QuantityInteger5 += 1
End Select
CardTypeArray(PlayerQuantitySubtractionInteger) = Player1HandGroup(Number).CardType
CardCheckBoxArray(TextBoxInteger).Text = Player1HandGroup(Number).CardNameString
NumberArray(PlayerQuantitySubtractionInteger) = Number
Next PlayerQuantitySubtractionInteger
End If
End If
End Sub

Pass through the list and determine only those that are valid. Then randomly pull from that set. Here is a simple version of it. You could use LINQ as well, but this should be clear enough:
Dim validDeckGroupsIndexes As New List(Of Integer)
For ndx As Integer = 0 to DeckGroup.Count - 1
If DeckGroup(ndx).QuantityInteger > 0 Then
validDeckGroupsIndexes .Add(ndx)
End If
Next ndx
Then use this:
Dim deckGroupNdx As Integer = Rnd.Next(0, validDeckGroupsIndexes.Count)
Number = DeckGroup(deckGroupNdx).ID

Related

Can I optimize my for loop using LINQ in vb.net?

I have a for loop that is working as intended. I iterate over an array of values and replace bad values (-99999) with the closest good value (if the bad value is on either end of the array) or the average of the two surrounding values (if the bad value is in the middle of the array). However, these nested loops smelled to me and I am looking for any suggested means of optimizing this (in terms of readability or performance).
I could access or remove the bad values using Dim badValues = MeasuredValues.Where(Function(x) x = -99999) but I'm not sure how I can keep track of the indexes for calculating the replacement value when using .Where. I'm no vb.net expert.
For index = 0 To MeasuredValues.Count - 1
If MeasuredValues(index) = -99999 Then
Dim nextGoodValue = -99999
Dim previousGoodValue = -99999
Dim replacementValue = -99999
For j = index To MeasuredValues.Count - 1
If MeasuredValues(j) <> -99999 Then
nextGoodValue = MeasuredValues(j)
Exit For
End If
Next
For k = index To 0 Step -1
If MeasuredValues(k) <> -99999 Then
previousGoodValue = MeasuredValues(k)
Exit For
End If
Next
If previousGoodValue = -99999 Then
replacementValue = nextGoodValue
ElseIf nextGoodValue = -99999 Then
replacementValue = previousGoodValue
Else
replacementValue = (nextGoodValue + previousGoodValue) / 2
End If
MeasuredValues(index) = replacementValue
End If
Next
Thanks for your time, please let me know if you need more info or have any questions.

Visual Basic dividing

I'm on visual basic and I'm reading through some piece of code my teacher wrote, he had this chunck of code:
Private Sub btnDividing_Click(sender As Object, e As EventArgs) Handles btnDividing.Click
Dim number As Integer = InputBox("Divide number by 2:")
Dim result As Integer = 0
Do While (number <> 0)
result += 1
number = number - 2
Loop
MsgBox("The result is: " & result, MsgBoxStyle.Exclamation)
End Sub
So my teacher typed the result += 1 and number = number -2 I didn't really understand that part so i tried simplifying it by changing it to:
Dim number As Integer = InputBox("Divide number by 2:")
Dim result As Integer = 0
Do While (number <> 0)
result = number / 2
Loop
MsgBox("The result is: " & result, MsgBoxStyle.Exclamation)
End Sub
but it keeps freezing after I click "OK"
Any suggestions?
It freezes because you made it an infinite loop:
Do While (number <> 0)
result = number / 2
Loop
The loop checks the value of number, but your modified code in the loop never modifies the value of number. So if the condition is true the first time it's checked, it will always be true. The original code modified the value:
Do While (number <> 0)
result += 1
number = number - 2
Loop
Since number is decremented by 2 with each iteration of the loop, it will eventually (assuming it's even) be equal to 0, making the loop condition false and the loop will end.
Basically, a loop needs to in some way modify the values being checked in the condition (or have some other control statement to exit the loop) or the loop will infinitely run.

Looping through only columns containing values for specific row in dataTable

I am developing an application using Visual Basic 2010 for hydraulic calculations of a pipe network.
This application uses a lot of iterations and loops, depending on the user input and size of network. Most of the results have to be saved temporarily to be used for the next step of calculations.
Firstly, I used a DataGridView to save the results but as the number of iterations increased, the application became very slow.
Now I am trying to create a DataTable, then populate it with some initial results (this part was successful). The obtained DataTable has some columns that are not populated like so:
22 24 10
3 16 22 9 15
16 12 24 13
14 21 10 23 12 1
24 18 23 2 1
Other calculations are performed and a certain value (X) is obtained.
Now I am trying to loop through the columns of a specific row to check if the calculated value (X) equals to one of the values in those columns.
My question is: How can I loop through only the columns that have values (avoiding the columns containing NULL values) for a specific row?
I am a beginner in VB.net. I hope my question is clear as I didn't provide any code.
Thanks in advance for you help.
This is the initial code I used:
Results.DGVInitial.Rows.Clear()
Results.DGVFinal.Rows.Clear()
For m As Integer = 0 To NetworkLayout.DGVNetworkLayout.Rows.Count - 1
Results.DGVInitial.Rows.Add()
Next
Dim I As Integer = NetworkLayout.DGVNetworkLayout.Rows.Count - 1
Dim Sec(I), Ini(I) As Integer
Dim Hyd(I), Dia(I), Len(I) As Single
Dim Qsec(I), Qini(I), Vsec(I) As Single
Dim U(I), Y(I) As Single
Do
I = I - 1
Sec(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(0).Value
Ini(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(1).Value
Hyd(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(6).Value
Dia(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(4).Value
Len(I) = NetworkLayout.DGVNetworkLayout.Rows(I).Cells(3).Value
Dim V As Integer
V = Results.DGVRandomGen.Rows(TotalNum_Runs - 1).Cells(I).Value
Qsec(I) = 0
Dim q As Single = 0
For n As Integer = 0 To Results.DGVInitial.Rows.Count - 1
If Results.DGVInitial.Rows(n).Cells(1).Value = Sec(I) Then
q = Results.DGVInitial.Rows(n).Cells(0).Value
Qsec(I) = Qsec(I) + q
Else
Qsec(I) = Qsec(I)
End If
Next
If V = 1 Then ' if the hydrant is open
Qini(I) = Hyd(I) + Qsec(I)
Else ' if the hydrant is close
Qini(I) = Qsec(I)
End If
Results.DGVInitial.Rows(I).Cells(0).Value = Qini(I)
Results.DGVInitial.Rows(I).Cells(1).Value = Ini(I)
Results.DGVSectionDischarges.Rows(TotalNum_Runs - 1).Cells(I).Value = ini(I).ToString("F2")
Now instead of using
V = Results.DGVRandomGen.Rows(TotalNum_Runs - 1).Cells(I).Value
I would like to replace the "DGVRandomGen" with a DataTable called "DT_Random"
Like I said I am a beginner so I am not sure how to code it but it will be something like this:
For DT_Random.Rows (TotalNum_Runs - 1)
For Each col As DataColumn In DT_Random.Columns
If DT_Random.Rows(TotalNum_Runs - 1).Item(col) = I Then
Qini(I) = Hyd(I) + Qsec(I)
Else
Qini(I) = Qsec(I)
End If
Next
But I want to avoid Null values as not all columns are populated
Thanks
Maybe this will help you:
Dim myXvalue = 24
Dim myDataTable As New DataTable
myDataTable.Columns.Add("Col1")
myDataTable.Columns.Add("Col2")
myDataTable.Columns.Add("Col3")
myDataTable.Columns.Add("Col4")
myDataTable.Rows.Add(22, 24, 10, DBNull.Value)
myDataTable.Rows.Add(3, 16, 22, DBNull.Value)
myDataTable.Rows.Add(24, 18, DBNull.Value, 24)
For Each column As DataColumn In myDataTable.Columns
If IsDBNull(myDataTable.Rows(0).Item(column)) Then
MsgBox("DB Null Found At: " & column.ColumnName)
Continue For
End If
If myDataTable.Rows(0).Item(column) = myXvalue Then
MsgBox("Match: " & myDataTable.Rows(0).Item(column) & " found at " & column.ColumnName)
End If
Next column
Just a quick example, you may need to restructure it a bit, but at least it shows you how to access the values in your datatable by columns. I would do a function that passes a row index as a parameter and returns a boolean. Create two booleans inside the sub, one for dbnull existing in the row, and one for finding a matching value. If dbnull bool is false, and match value is true, then return true. Just make sure you loop all the columns and dont exit early.
If you need me to elaborate let me know.

Find Common Values From Datagridview

I Have about 10 (DatagridView Count may varies As per User Selected Files From 2 to 10) Datagridview ,So How can i find common value from all Datagridviews ??
Comment If you need more brief details
Below is mine but It find common from 2 -2 datagridviews
For i As Integer = 1 To dgvCont
For j As Integer = 0 To Main.DGVM(i).Rows.Count - 1
For Each Val As DataGridViewRow In Main.DGVM(i + 1).Rows
If Val.Cells(0).Value = Main.DGVM(i).Rows.Item(j).Cells(0).Value Then
Dim cm As String = Val.Cells(0).Value
If cm = "" Then
Else
Analysis.lvCmn.Items.Add(Val.Cells(0).Value)
End If
End If
Next
Next
Next
I understand that you want to set two nested loops accounting for an undetermined number of elements (items in an array of DataGridView, I presume), performing the checks you want:
For count1 As Integer = 1 To dgvCont 'Assuming indices from 1 to dgvCont
For row1 As Integer = 0 To Main.DGVM(count1).Rows.Count - 1
If (Main.DGVM(count1).Rows(row1).Cells(0).Value Is Nothing) Then Continue For
Dim val1 As String = Main.DGVM(count1).Rows(row1).Cells(0).Value
Dim found As Boolean = False
For count2 As Integer = 1 To dgvCont 'Assuming indices from 1 to dgvCont
If (count2 = count1) Then Continue For
For row2 As Integer = 0 To Main.DGVM(count2).Rows.Count - 1
If (Main.DGVM(count2).Rows(row2).Cells(0).Value Is Nothing) Then Continue For
Dim val2 As String = Main.DGVM(count2).Rows(row2).Cells(0).Value.ToString()
If val1 = val2 Then
Dim cm As String = val1
If cm = "" Then
Else
Analysis.lvCmn.Items.Add(val1)
End If
found = True
Exit For 'By assuming that you want to stop searching after finding a match
End If
Next
If (found) Then Exit For 'By assuming that you want to stop searching after finding a match
Next
Next
Next
Your code is not too clear (neither what you want); but this should give you a good enough start to carry out the implementation you are looking for. Bear in mind that this code (like yours) only considers one column (first one); in case of wanting to iterate through all the columns, you would have to add further nested loops accounting for that.

Random numbers in array without any duplicates

I'm trying to randomize an array from numbers 0 to 51 using loops but I just cannot seem to pull it off. My idea was that
Generate a Random Number
Check if this random number has been used by storing the previous in an array
If this random number has been used, generate new random number until it is not a duplicate
If it's not a duplicate, store it
My attempt:
Dim list(51) As Integer
Dim templist(51) As Integer
For i As Integer = 0 To 51 Step 1
list(i) = i
Next i
Do While counter <= 51
p = rand.Next(0, 52)
templist(counter) = p
For n As Integer = 0 To 51 Step 1
p = rand.Next(0, 52)
If templist(n) = p Then
Do While templist(n) = p
p = rand.Next(0, 52)
Loop
templist(n) = p
Else
templist(n) = p
End If
Next
counter += 1
Loop
For n As Integer = 0 To 51 Step 1
ListBox1.Items.Add(templist(n))
Next
It will be a lot easier if you just have a list of all of the possible numbers (0 to 51 in your case), then remove the number from the list so it can't be picked again. Try something like this:
Dim allNumbers As New List (Of Integer)
Dim randomNumbers As New List (Of Integer)
Dim rand as New Random
' Fill the list of all numbers
For i As Integer = 0 To 51 Step 1
allNumbers.Add(i)
Next i
' Grab a random entry from the list of all numbers
For i As Integer = 0 To 51 Step 1
Dim selectedIndex as Integer = rand.Next(0, (allNumbers.Count - 1) )
Dim selectedNumber as Integer = allNumbers(selectedIndex)
randomNumbers.Add(selectedNumber)
allNumbers.Remove(selectedNumber)
' Might as well just add the number to ListBox1 here, too
ListBox1.Items.Add(selectedNumber)
Next i
If your goal is to get the numbers into ListBox1, then you don't even need the "randomNumbers" list.
EDIT:
If you must have an array, try something like this:
Function RandomArray(min As Integer, max As Integer) As Integer()
If min >= max Then
Throw New Exception("Min. must be less than Max.)")
End If
Dim count As Integer = (max - min)
Dim randomNumbers(count) As Integer
Dim rand As New Random()
' Since an array of integers sets every number to zero, and zero is possibly within our min/max range (0-51 here),
' we have to initialize every number in the array to something that is outside our min/max range.
If min <= 0 AndAlso max >= 0 Then
For i As Integer = 0 To count
randomNumbers(i) = (min - 1) ' Could also be max + 1
Next i
End If
Dim counter As Integer = 0
' Loop until the array has count # of elements (so counter will be equal to count + 1, since it is incremented AFTER we place a number in the array)
Do Until counter = count + 1
Dim someNumber As Integer = rand.Next(min, max + 1)
' Only add the number if it is not already in the array
If Not randomNumbers.Contains(someNumber) Then
randomNumbers(counter) = someNumber
counter += 1
End If
Loop
Return randomNumbers
End Function
This is good enough for your assignment, but the computer scientist in my hates this algorithm.
Here's why this algorithm is much less desirable. If zero is in your range of numbers, you will have to loop through the array at least 2N times (so 104+ times if you are going from 0 to 51). This is a best case scenario; the time complexity of this algorithm actually gets worse as the range of numbers scales higher. If you try running it from 0 to 100,000 for example, it will fill the first few thousand numbers very quickly, but as it goes on, it will take longer and longer to find a number that isn't already in the list. By the time you get to the last few numbers, you could potentially have randomly generated a few trillion different numbers before you find those last few numbers. If you assume an average complexity of 100000! (100,000 factorial), then the loop is going to execute almost ten to the half-a-millionth power times.
An array is more difficult to "shuffle" because it is a fixed size, so you can't really add and remove items like you can with a list or collection. What you CAN do, though, is fill the array with your numbers in order, then go through a random number of iterations where you randomly swap the positions of two numbers.
Do While counter <= 51
p = rand.Next(0, 52)
While Array.IndexOf(list, p) = -1
p = rand.Next(0, 52)
End While
counter += 1
Loop
Haven't written VB in about 5 years, but try this out:
Function GetRandomUniqueNumbersList(ByVal fromNumber As Integer, ByVal toNumber As Integer) As List(Of Integer)
If (toNumber <= fromNumber) Then
Throw New ArgumentException("toNumber must be greater than fromNumber", toNumber)
End If
Dim random As New Random
Dim randomNumbers As New HashSet(Of Integer)()
Do
randomNumbers.Add(random.Next(fromNumber, toNumber))
Loop While (randomNumbers.Count < toNumber - fromNumber)
Return randomNumbers.ToList()
End Function
Ok, that was painful. Please someone correct it if I made any mistakes. Should be very quick because it's using a HashSet.
First response to forum on stackoverflow - be gentle.
I was looking for a way to do this but couldn't find a suitable example online.
I've had a go myself and eventually got this to work:
Sub addUnique(ByRef tempList, ByVal n, ByRef s)
Dim rand = CInt(Rnd() * 15) + 1
For j = 0 To n
If tempList(j) = rand Then
s = True
End If
Next
If s = False Then
tempList(n) = rand
Else
s = False
addUnique(tempList, n, s)
End If
End Sub
Then call the sub using:
Dim values(15) As Byte
Dim valueSeen As Boolean = False
For i = 0 To 15
addUnique(values, i, valueSeen)
Next
This will randomly add the numbers 1 to 16 into an array. Each time a value is added, the previous values in the array are checked and if any of them are the same as the randomly generated value, s is set to true. If a value is not found (s=false), then the randomly generated value is added. The sub is recursively called again if s is still true at the end of the 'For' loop. Probably need 'Randomize()' in there somewhere.
Apologies if layout is a bit wobbly.