What is the most efficient method for looping through a SortedList in VB 2008? - vb.net

The code below shows me (I think) that the "for each" loop is about 10% faster than the "i to n" loop, but the "for each" loop creates 567k in new memory? Is this right? Which way is generally most efficient with regards to speed and memory usage?
If you want to run this code in VB just add a button and 2 labels to a form.
Public Class StateObject
Public WorkSocket As String = "FFFFFFFFFFFF"
Public BufferSize As Integer = 32767
Public Buffer(32767) As Byte
End Class
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
For cnt As Integer = 1 To 250
Dim StateObjecter As New StateObject
ClientNetList.Add(cnt.ToString, StateObjecter)
Next
End Sub
Private ClientNetList As New SortedList(Of String, StateObject)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim stop1 As New Stopwatch
Dim stop2 As New Stopwatch
Dim TotalMemory1 As Integer = GC.GetTotalMemory(False)
stop1.Start()
For cnt As Integer = 1 To 1000000
For i = 0 To ClientNetList.Count - 1
ClientNetList.Values(i).WorkSocket = "FFF"
Next
Next
stop1.Stop()
Dim TotalMemory2 As Integer = GC.GetTotalMemory(False)
MsgBox(TotalMemory2 - TotalMemory1)
TotalMemory1 = GC.GetTotalMemory(False)
Dim fff As Integer = GC.GetGeneration(ClientNetList)
stop2.Start()
For cnt As Integer = 1 To 1000000
For Each ValueType As StateObject In ClientNetList.Values
ValueType.WorkSocket = "FFF"
Next
Next
stop2.Stop()
Dim ffff As Integer = GC.GetGeneration(ClientNetList)
TotalMemory2 = GC.GetTotalMemory(False)
MsgBox(TotalMemory2 - TotalMemory1)
Label1.Text = "i: " & stop1.ElapsedMilliseconds
Label2.Text = "e: " & stop2.ElapsedMilliseconds
End Sub

On my system the "for i = 1 " loop was faster for the first test (the first click of the button of the program run) by about 20 percent. But the "for each" loop was a hair faster on subsequent tests. The "for each" loop took a little more memory, but this is temporary and will be eventually garbage collected.
The pros and cons of "for each" and "for i = " have been debated here. For each is nice because it works with structures other than arrays, and makes an object available. "For i =" has the advantage of designating the bounds and order of array items in the loop, and avoids a bug you can encounter with arrays:
Dim a(50) As Integer
Dim i As Integer
For Each i In a
i = 22
Next i
In this example, the array is never initialized to 22. The variable i is just a copy of an array element, and the original array element is not changed when i is assigned 22.

Related

Calculate cost of several items with tax and a discount

Can anyone help with this school task I have
The task is to ask the user for items and the cost of the items until they chose to stop. Then combine all the costs and take 20% VAT and 10% off from 2 randomly selected items.
Here is the code I have so far (I have 2 buttons and a listbox)
Public Class Form1
Dim CurrentA As Integer
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim Items(CurrentA) As String
Dim Coins(CurrentA) As Single
Dim Stay As String
CurrentA = 0
Do Until CurrentA = 20
Items(CurrentA) = InputBox("Please Enter The Item")
Coins(CurrentA) = InputBox("Please Enter The Cost Of The Item")
Stay = InputBox("Type Yes If More Items or Type No if no More")
Stay = Stay.ToLower
If Stay = "yes" Then
End If
If Stay = "no" Then
Exit Do
End If
ListBox1.Items.Add(Items(CurrentA) & " " & Coins(CurrentA))
CurrentA += 1
Loop
End Sub
End Class
First, a few comments on the code you presented.
Dim CurrentA As Integer
'An Integers default value is zero, I don't see why this is a class level variable
'always declare variables with as narrow a scope as possible
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim Items(CurrentA) As String 'Declares an Array of Type String with an Upper Bound of 0
'Upper Bound is the highest index in the array
'Arrays start with index 0
'So your array will have 1 element at index 0
Dim Coins(CurrentA) As Single
Dim Stay As String
CurrentA = 0 'unnecessary because the default of CurrentA is already 0, but OK for clarity because it could have been changed elsewhere
'This is behaving like a console application with the code repeating in a loop.
'In Windows Forms it would be more likely to do this in a button click event (btnAddItem)
Do Until CurrentA = 20
'On the first iteration CurrentA = 0
'On the second iteration CurrentA = 1 - this exceeds the size of your array
'and will cause an index out of range error
Items(CurrentA) = InputBox("Please Enter The Item")
'With Option Strict on you must change the input to a Single
Coins(CurrentA) = CSng(InputBox("Please Enter The Cost Of The Item"))
Stay = InputBox("Type Yes If More Items or Type No if no More")
Stay = Stay.ToLower 'Good! The user might no follow directions exactly
If Stay = "yes" Then
'This is kind of silly because it does nothing
End If
'Lets say I say no on the first iteration
'This avoids the index out of range error but
'nothing is added to the list because you Exit the loop
'before adding the item to the ListBox
If Stay = "no" Then
Exit Do
End If
ListBox2.Items.Add(Items(CurrentA) & " " & Coins(CurrentA))
CurrentA += 1
Loop
End Sub
We could use arrays but not knowing how many items will be added means either making the array bigger than needed or using Redim Preserve on every addition. A much better choice is a List(Of T). They work a bit like arrays but we can just add items without the ReDim stuff.
Private lstCost As New List(Of Single)
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
'Pretend this button is called btnAdd, and you have 2 test boxes
lstCost.Add(CSng(TextBox2.Text))
'The $ introduces an interpolated string. It is a step up form String.Format
ListBox2.Items.Add($"{TextBox1.Text} - {CSng(TextBox2.Text):C}") 'The C stands for currency
TextBox1.Clear()
TextBox2.Clear()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'Pretend this button is called btnTotal
'Dim total As Single = (From cost In lstCost
' Select cost).Sum
Dim total As Single = lstCost.Sum
Label1.Text = total.ToString("C") 'C for Currency
End Sub

Alternative Process

I have 2 buttons and a DataGridView with 2 Columns (0 & 1).
The 1st button transfers a randomized cell from the Column(1) to a TextBox. Then, it stores that Cell in variable (a), plus the cell that opposites it in variable (b).
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim rnd As New Random
Dim x As Integer = rnd.Next(0, Form1.DataGridView1.Rows.Count)
Dim y As Integer = 1
Dim a As String = Form1.DataGridView1.Rows(x).Cells(y).Value
Dim b As String = Form1.DataGridView1.Rows(x).Cells(y - 1).Value
TextBox3.Text = a
End Sub
The 2nd button, however, is supposed to compare if another TextBox's text has the same string variable (b) has as Strings. Now, if so, then it has to display a certain message and so on...
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If TextBox4.Text = b Then '<<< ISSUE HERE!
MsgBox("Correct! ^_^")
ElseIf TextBox4.Text = "" Then
MsgBox("You have to enter something first! O_o")
Else
MsgBox("Wrong! >,<")
End If
End Sub
The problem is that the variable (b) is surely not shared across the two "private" subs. And so, there is NOTHING to compare to in the 2nd button's sub! I presume that the solution here is to split the "randomization process" into a separate function, then execute it directly when the 1st button gets activated. Furthermore, that function's variables have to be SHARED somehow, and I certainly don't know how!
Thanks for Mr. Olivier, the code has been improved significantly! Yet, I still encounter a "wrong" comparison issue, somehow!
Dim RND As New Random
Dim x As Integer
Private Function GetCell(ByVal rowIndex As Integer, ByVal cellIndex As Integer) As String
Return Form1.DataGridView1.Rows(rowIndex).Cells(cellIndex).Value
End Function
Private Sub btnRoll_Click(sender As Object, e As EventArgs) Handles btnRoll.Click
x = RND.Next(0, Form1.DataGridView1.Rows.Count)
tbxRoll.Text = GetCell(x, 1)
End Sub
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
If tbxSubmit.Text = GetCell(x, 0) Then
MsgBox("Correct! ^_^")
ElseIf tbxSubmit.Text = "" Then
MsgBox("You have to enter something first! O_o")
Else
MsgBox("Wrong! >,<")
End If
End Sub</code>
Well, unbelievably, I read a guide about "comparison operations" in VB.net and tried out the first yet the most primal method to compare equality - which was to use .Equals() command - and worked like a charm! Thank God, everything works just fine now. ^_^
If tbxSubmit.Text.Equals(GetCell(x, 0)) Then
Alright now... This is going to sound weird! But, following Mr. Olivier's advise to investigate "debug" the code, I rapped the string I'm trying to compare with brackets and realized that it's been outputted after a break-line space! So, I used the following function to remove the "white-space" from both of the comparison strings! And it bloody worked! This time for sure, though. ^_^
Function RemoveWhitespace(fullString As String) As String
Return New String(fullString.Where(Function(x) Not Char.IsWhiteSpace(x)).ToArray())
End Function
If RemoveWhitespace(tbxSubmit.Text) = RemoveWhitespace(GetCell(x, 0)) Then
Turn the local variables into class fields.
Dim rnd As New Random
Dim x As Integer
Dim y As Integer
Dim a As String
Dim b As String
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
x = rnd.Next(0, Form1.DataGridView1.Rows.Count)
y = 1
a = Form1.DataGridView1.Rows(x).Cells(y).Value
b = Form1.DataGridView1.Rows(x).Cells(y - 1).Value
TextBox3.Text = a
End Sub
These fields can now be accessed from every Sub, Function and Property.
Of course Button3_Click must be called before Button2_Click because the fields are initialized in the first method. If this is not the case then you should consider another approach.
Create a function for the Cell access
Private Function GetCell(ByVal rowIndex As Integer, ByVal cellIndex As Integer) _
As String
Return Form1.DataGridView1.Rows(rowIndex).Cells(cellIndex).Value
End Function
And then compare
If TextBox4.Text = GetCell(x, y - 1) Then
...
And don't store the values in a and b anymore. If y is always 1 then use the numbers directly.
If TextBox4.Text = GetCell(x, 0) Then
...
One more thing: give speaking names to your buttons in the properties grid before creating the Click event handlers (like e.g. btnRandomize). Then you will get speaking names for those routines as well (e.g. btnRandomize_Click).
See:
- VB.NET Class Examples
- Visual Basic .NET/Classes: Fields

Visual Basic Windows Form - Help Loading picture corresponding to numerical value

Hello i am trying to make a texas hold'em style game and im at the point where i filled an array with numbers 1-52 randomly assorted. I first pull the value from the first index of the array and have the corresponding card picture be set to a picturebox value. I have saved the 52 card .png files in my resources as well. These pictures are also saved with their names as 1.png, 2.png, 3.png.... etc depending the suit and value.
I am sorting the values as 1-13 spades (2-ace), 14-26 hearts, 27-39 diamonds, 40-52 clubs.
I also just saw i should probably use a global counter to keep track of the deck position.
Public Class Form1
Dim Deal As MsgBoxResult
Dim CardDeck As New Random
Dim Counter As Integer = 1
Dim CardCount(52) As Integer
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles btnStart.Click
Deal = MessageBox.Show("Would you like to start a Game?", "Texas Holde'em", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
ShuffleDeck()
CalculateFirst3Cards()
End Sub
Private Sub ShuffleDeck()
If Deal = MsgBoxResult.Yes Then
For num As Integer = 1 To CardCount.Length - 1
Dim DeckValue As Integer = CardDeck.Next(1, 52)
CardCount(Counter) = DeckValue
Counter += 1
Next
End If
End Sub
Private Sub CalculateFirst3Cards()
Dim counter As Integer = 1
For num As Integer = 1 To 3
Dim hold As Integer = CardCount(counter)
Dim hold1 As String = Convert.ToString(hold)
River1.Image = My.Resources.
counter += 1
Next
End Sub
End Class

How To Clear CheckedListBox From Previous Selection - VB.NET

Okay so I have a checkedlistbox that when a user selects a item it will print out that item into word. That works perfect. However, I want to give the user the option to not select anything at all, but when the user does not select an item in the checkboxlist, it still prints out the previous selected item into MS word.
Below is my code:
Private Sub ExportContactOkButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExportContactOkButton.Click
Dim i As Integer
Dim array_Counter_Contacts As Integer
Dim array_size_Contacts As Integer
array_Counter_Contacts = 0
For i = 0 To ExportContactCheckedListBox.Items.Count() - 1
If ExportContactCheckedListBox.GetItemCheckState(i) = CheckState.Checked Then
array_size_Contacts = UBound(SelectedContacts)
ReDim Preserve SelectedContacts(array_size_Contacts + 1)
SelectedContacts(array_Counter_Contacts) = ExportContactCheckedListBox.Items(i)
If Search.debugging = True Then
MsgBox(ExportContactCheckedListBox.Items(i))
End If
array_Counter_Contacts += 1
ExportContactCheckedListBox.Items(i) = ExportContactCheckedListBox.Items(i).ToString.Replace("'", "''")
If array_Counter_Contacts = 1 Then
ContactNames = "" & ExportContactCheckedListBox.Items(i) & ""
Else
ContactNames = String.Concat(ContactNames & "', '" & ExportContactCheckedListBox.Items(i) & "")
End If
End If
Next
If Search.debugging = True Then
MsgBox(ContactNames)
End If
sConnection.Close()
Dispose()
Me.Close()
End Sub
I have tried remove, clear, and I even tried this
Dim i As Integer
For i = 0 To ExportContactCheckedListBox.CheckedIndices.Count - 1
ExportContactCheckedListBox.SetItemChecked(ExportContactCheckedListBox.CheckedIndices(0), False)
Next i
But nothing is working. Can anyone help me? All I want is to be able to have the checkedlistbox forget or clear the checked item after the "OK" button is pressed and the text has already been printed into word.
Use a List(Of String) to store the selection and, of course remember, to reinitialize the list when you hit the ExportContactOkButton
' Declared at the form level....
Dim SelectedContacts as List(Of String)
.....
Private Sub ExportContactOkButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExportContactOkButton.Click
Dim i As Integer
SelectedContacts = new List(Of String)()
For i = 0 To ExportContactCheckedListBox.Items.Count() - 1
If ExportContactCheckedListBox.GetItemCheckState(i) = CheckState.Checked Then
SelectedContacts.Add(ExportContactCheckedListBox.Items(i))
.....
End If
Next
End Sub
In this way, every time you hit the ExportContactOKButton you reinitialize your list of contacts, loop through the checked items, add the checked one to your list.
A List(Of String) is better because you don't need to know in advance how many items your user selects in the CheckedListBox and continuosly resize the array. You simply add new items to it. And you could always use it like an array
Dim Dim array_Counter_Contacts = SelectedContacts.Count
For i = 0 to array_Counter
Console.WriteLine(SelectedContacts(i))
Next

Why VB.NET simple app takes forever to load several lines

I have a super simple script that I am using to separate a long list of phone numbers we've gathered from donors over the years into separate area code files.
Obviously, when you have almost 1 million lines it's going to take a while - BUT - if I put in 1,000 it takes less than a second. Once I put in 1 million it takes 10 seconds to do only 5 lines. How could this be?
Imports System.IO
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Dim lines As String
lines = RichTextBox1.Lines.Count
Dim looper As String = 0
Dim path As String = "c:\acs\"
MsgBox("I have " + lines + " lines to do.")
If (Not System.IO.Directory.Exists(path)) Then
System.IO.Directory.CreateDirectory(path)
End If
Dim i As String = 0
For loops As Integer = 0 To RichTextBox1.Lines.Count - 1
Dim ac As String = RichTextBox1.Lines(looper).ToString
ac = ac.Substring(0, 3)
Dim strFile As String = path + ac + ".txt"
Dim sw As StreamWriter
sw = File.AppendText(strFile)
sw.WriteLine(RichTextBox1.Lines(looper))
sw.Close()
Label1.Text = String.Format("Processing item {0} of {1}", looper, lines)
looper = looper + 1
Next
MsgBox("done now")
End Sub
End Class
Each time you use the RichTextBox.Lines properties, VB.Net will need to split the content by CR+LF pair. Thus your For loops As Integer = - To RichTextBox1.Lines.Count-1 is really a performance hit.
Try to use:
For Each vsLine As String in RichTextBox1.Lines
instead. It will be a lot faster. Alternatively, if you must use For loop, then get it once:
Dim vasLines() As String = RichTextBox1.Lines
For viLines As Integer = 0 to UBound(vasLines.Count)
'....
Next
instead.
First, you're performing UI updates in your For loop. That will take time.
You're updating the UI in a thread that is not the main thread which might impact performance. You should not use the CheckForIllegalCrossThreadCalls method. You should update the UI properly using the ReportProgress method of the BackgroundWorker.
You are opening and closing a file for each iteration of the loop. That will take time as well.
I think a better method would be to add the data to a Dictionary(Of String, List(Of String)), with the area code as the key and the list would hold all the number for that area code. Once the Dictionary is filled, loop through the keys and write the numbers out.