VB Moving several indexes at one time - vb.net

Just wanted to ask a question on moving several indexes of an array at once while in a sorting algorithm.
Basically I'm using a bubblesort algorithm (in VB.NET) to sort a list of data which contains names, height, weight and age. Each name is essentially associated with each extra piece of data. This data is sent in from a txt file and then sorted using the algorithm. This file can also be edited to add new names or pieces of data.
Is there a way I am able to associate the pieces of data so that when the array is sorted the data stays with the names while the names have been sorted alphabetically?
Example of how txt file is set out and sorting method:
Unsorted:
Monty Reyes
28
1700
70.7
Kier Burke
45
1800
93.5
Sorted:
Kier Burke
45
1800
93.5
Monty Reyes
28
1700
70.7
My current code is just a simple bubblesort which sorts the entire array.
Private Sub btnNameSort_Click(sender As Object, e As EventArgs) Handles btnNameSort.Click
'turn the listbox items found in lstCurrentData to an array()
Dim Data() As String = lstCurrentData.Items.Cast(Of Object).Select(Function(o) lstCurrentData.GetItemText(o)).ToArray
Dim temp As String
For ii = 0 To Data.Length - 2
For i = 0 To Data.Length - 2
If Data(i) > Data(i + 1) Then
temp = Data(i)
Data(i) = Data(i + 1)
Data(i + 1) = temp
End If
Next
Next
For Each st In Data
lstSortArray.Items.Add(st)
Next
End Sub
If anyone has an idea on a way to create these associations within an array please inform me, I truly have no idea where to go from here. I tried to use Array.Copy but it got to complicated for me and I couldn't understand anything, I tried moving the new index in front of the name but then realised that would create an infinite loop. If there is a variable or something on this earth please just give me an idea of where to go. If anymore clarification is needed just ask. I'm still pretty new to VB so if I don't understand some terminology forgive me.

If you make a class for the data (Objects and classes in Visual Basic), you can use LINQ (Introduction to LINQ in Visual Basic) to sort it on whichever property you like.
To demonstrate, I made this as a Console App for simplicity, and you will have to write the part to read in and parse the data from the file:
Module Module1
Public Class Person
Property Name As String
Property Age As Integer
Property Height As Integer
Property Weight As Double
Public Overrides Function ToString() As String
Return String.Format("({0}: {1} years old, {2} mm, {3} kg)", Name, Age, Height, Weight)
End Function
End Class
Public Sub ShowPeople(people As IEnumerable(Of Person))
Console.WriteLine(String.Join(vbCrLf, people))
End Sub
Public Sub BubbleSortPeopleByName(ByVal people As List(Of Person))
For i = 0 To people.Count - 2
Dim doneSwap = False
For j = i + 1 To people.Count - 1
If people(i).Name > people(j).Name Then
Dim tmp = people(j)
people(j) = people(i)
people(i) = tmp
doneSwap = True
End If
Next
If Not doneSwap Then Return
Next
End Sub
Sub Main()
Dim people As New List(Of Person)
'TODO: Load the data from a file.
people.Add(New Person With {.Name = "Monty Reyes", .Age = 28, .Height = 1700, .Weight = 70.7})
people.Add(New Person With {.Name = "Kier Burke", .Age = 45, .Height = 1800, .Weight = 93.5})
Console.WriteLine("Unsorted:-")
ShowPeople(people)
BubbleSortPeopleByName(people)
Console.WriteLine("Sorted:-")
ShowPeople(people)
Console.ReadLine()
End Sub
End Module
Outputs:
Unsorted:-
(Monty Reyes: 28 years old, 1700 mm, 70.7 kg)
(Kier Burke: 45 years old, 1800 mm, 93.5 kg)
Sorted:-
(Kier Burke: 45 years old, 1800 mm, 93.5 kg)
(Monty Reyes: 28 years old, 1700 mm, 70.7 kg)
The Person class has its own .ToString() method to make it easy to show an instance of Person as a string.
The ShowPeople method has people As IEnumerable(Of Person) as its parameter instead of a List(Of Person) because a List is an IEnumerable but not vice versa, and it makes it more versatile.

Related

Visual Basic Text File

I'm currently learning about Visual Basic text files but I came across a problem. I'm supposed to create a text file (Players) with data inside and I have to design a form with listbox to include the players’ names that are more than 30 years old.
This is my current code:
Dim q1 = From itm As String In IO.File.ReadAllLines("Players.txt")
Let Data=itm.Split(","c)
Let fname = Data(0)
Let age = Data(4)
Let newline = fname * " "& age
Where age > 30
For Each itm1 As String in q1
ListBox1.Items.Add(itm1)
Next
My expected output should show the names of players that are over 30 years old. Thank you in advance to anyone that can help me solve this issue.
You can use linq. For example: assume you have a txt like that
Giuseppe, 30
Pippo, 13
Luca, 32
to extract only over 30 years old you can do...
Dim obj = My.Computer.FileSystem.ReadAllText("Players.txt").Split(vbCrLf).ToList()
Dim ret = (From a In obj Where a.Split(",")(1) > 30 Select a).ToList
The result is
Luca, 32
Best to use a class to define Player. I also made a class Players to hide the file processing from the consumer.
Public Class Player
Public Property Name As String
Public Property Age As Integer
End Class
Public Class Players
Private _list As New List(Of Player)()
Public ReadOnly Property List As IEnumerable(Of Player)
Get
Return _list
End Get
End Property
Public Sub New(path As String)
Dim lines = File.ReadAllLines(path)
For Each line In lines
Dim split = line.Split(","c)
If split.Count = 2 Then
_list.Add(New Player() With {.Name = split(0), .Age = Integer.Parse(split(1))})
End If
Next
End Sub
End Class
And use databinding to populate the ListBox
Dim ps = New Players("Players.txt")
Me.ListBox1.DataSource = ps.Items.Where(Function(p) p.Age >= 30).ToList()
Me.ListBox1.DisplayMember = "Name"
If you're not into the whole Players class and Items property, you can still use the Player class, and just do all the processing in your consuming code (it's basically the same thing, but the processing code is not encapsulated in the model).
Dim ps = New List(Of Player)()
Dim lines = File.ReadAllLines("Players.txt")
For Each line In lines
Dim split = line.Split(","c)
If split.Count = 2 Then
ps.Add(New Player() With {.Name = split(0), .Age = Integer.Parse(split(1))})
End If
Next
Me.ListBox1.DataSource = ps.Where(Function(p) p.Age >= 30).ToList()
Me.ListBox1.DisplayMember = "Name"

Combine three text files

How does one combine three text files together into one? I was also trying to make it alphabetical by state in the new text file. I figured how to combine two but three I am getting lost.
Public Class newsenatefrm
Dim current() As String = IO.File.ReadAllLines("Senate113.txt")
Dim retired() As String = IO.File.ReadAllLines("RetiredSen.txt")
Dim newSen() As String = IO.File.ReadAllLines("NewSen.txt")
Dim queryCurrent = From line In current
Let state = Split(","c)(1)
Let name = Split(","c)(0)
Let party = Split(","c)(2)
Order By state Ascending
Select state, name, party
Dim queryRetired = From line In retired
Let state = Split(","c)(1)
Let name = Split(","c)(0)
Let party = Split(","c)(2)
Order By state Ascending
Select state, name, party
Dim queryNew = From line In newSen
Let state = Split(","c)(1)
Let name = Split(","c)(0)
Let party = Split(","c)(2)
Order By state Ascending
Select state, name, party
Private Sub generatebtn_Click(sender As Object, e As EventArgs) Handles generatebtn.Click
IO.File.WriteAllText("Senate114.txt")
End Sub
End Class
I included sample text from the three text files below:
Senate113.txt:
Richard Shelby,Alabama,R
Bernard Sanders,Vermont,I
Kristen Gillibrand,New York,D
Retired.txt:
John Rockefeller,West Virginia,D
Tom Coburn,Oklahoma,R
Carl Levin,Michigan,D
NewSen.txt:
Shelly Capito,West Virginia,R
Steve Daines,Montana,R
Gary Peters,Michigan,D
As you're just learning Visual Basic, you might enjoy finding some new things in this answer.
You've got files with the data separated by commas, a common format known as a comma-separated values file, or CSV file. There are several parsers available for that format, I'm just using the TextFieldParser Class because it comes with VB.NET.
If you're going to be working with data, it is very often a good idea to make a class for the data items - it allows you to keep associated data together, with sensible names, and provide methods that work with that data.
So, you could have one main List(Of Senator) to which you can add more data (senators) from a file, perhaps like this:
Imports System.IO
Imports Microsoft.VisualBasic.FileIO
Module Module1
Public Class Senator
Property Name As String
Property State As String
Property Party As String
Public Sub New()
' Empty constructor
End Sub
Public Sub New(name As String, state As String, party As String)
Me.Name = name
Me.State = state
Me.Party = party
End Sub
Public Overrides Function ToString() As String
Return $"{Name}, {State}, {Party}"
End Function
End Class
Function GetSenators(fromFile As String) As List(Of Senator)
Dim s As New List(Of Senator)
Using csvReader As New TextFieldParser(fromFile)
csvReader.Delimiters = {","}
While Not csvReader.EndOfData
Dim parts = csvReader.ReadFields()
If parts.Count = 3 Then
s.Add(New Senator(parts(0), parts(1), parts(2)))
End If
End While
End Using
Return s
End Function
Sub Main()
Dim srcDir = "C:\temp"
Dim srcFiles = {"Senate113.txt", "RetiredSen.txt", "NewSen.txt"}
Dim combinedSenators As New List(Of Senator)
For Each f In srcFiles
Dim actualFile = Path.Combine(srcDir, f)
combinedSenators.AddRange(GetSenators(actualFile))
Next
Dim senatorsByState = combinedSenators.OrderBy(Function(sen) sen.State).Select(Function(s) s.ToString())
Console.WriteLine(String.Join(vbCrLf, senatorsByState))
'File.WriteAllLines("C:\temp\Senate114.txt", senatorsByState)
Console.Write("Finished.")
Console.ReadLine()
End Sub
End Module
Which, with the sample data in the question, outputs:
Richard Shelby, Alabama, R
Carl Levin, Michigan, D
Gary Peters, Michigan, D
Steve Daines, Montana, R
Kristen Gillibrand, New York, D
Tom Coburn, Oklahoma, R
Bernard Sanders, Vermont, I
John Rockefeller, West Virginia, D
Shelly Capito, West Virginia, R
There are many possible ways, but as it looks like the files you're dealing with will be quite small, you could read them all into a List(Of String) to start with:
Dim a = IO.File.ReadLines("C:\temp\Senate113.txt").ToList()
a.AddRange(IO.File.ReadLines("C:\temp\RetiredSen.txt"))
a.AddRange(IO.File.ReadLines("C:\temp\NewSen.txt"))
Then carry on as you did before, you just need to do it once instead of thrice.
(It's best to give a full path to a file.)

Skip block of text from adding to dictionary vb.net

I want to know if there is a way to read a text file that lets say has content like so:
Store - 001P
Owner - Greg
Price - 45000
Employees - 30
Store - 002
Owner- Jen
Price - 34400
Now lets say I only want to work with the store information in the block where the store number contains a P. Is there a way to read the text file to check for the P after the delimiter, and then skip to the next instance of "store" in the text file? I tried to find which function is best as in different languages you can GoTo function, but I cannot find anything on this.
This is a snippet of my current code:
Dim columnV As New Dictionary(Of String, Integer)
Dim descriptionV As String
Dim quantityV As Integer
For Each totalLine As String In MyData
descriptionV = Split(totalLine, "-")(0)
quantityV = Split(totalLine, "-")(1)
If columnV.ContainsKey(descriptionV) Then
columnV(descriptionV) = colSums(descriptionV) + quantityV
Else
columnV.Add(descriptionV, quantityV)
End If
'Next
Ok, if I read this correct, you only want to worry about and process stores that end in a "P". Did I get that right?
and your data is like this then:
Store - 001P \n Owner - Greg \n Price - 45000 \n Employees - 30 \n
Store - 002\n Owner- Jen \n Price - 34400 \n
So in code we could go:
dim store as string
dim Owner as string
dim Price as decimal
dim employees as integer
' your code to read the data
now we have the process loop
For Each totalLine As String In MyData
store = trim(split(split(totalLine,"\n)(0),"-")(1))
Owner = trim(split(split(totalLine,"\n)(1),"-")(1))
Price = trim(split(split(totalLine,"\n")(3),"-")(1))
Employees = trim(split(split(totalLine,"\n")(4),"-")(1))
if strings.right(store,1) = "P" then
' our process code
If columnV.ContainsKey(store) Then
end if
Next
So you can use Strings.Right(some text,1) to get the right most character. You simply then have a if/then block around that code, and thus you skip anything that does not have a store with "P" as the last letter. The above is air code, but the simple concept here is to first:
Split out the line into the correct parts.
Check if store ends in "P" with the above if/then, and thus only stores ending in P will be processed.
However, your sample code and the variable names used really does not make a lot of sense.
It appears as if a record can be represented across multiple lines, but not a defined number of multiple lines (e.g. 001P is over 4 lines but 002 is over 3 lines).
It also appears as if the line represents the data in a {property} - {value} format.
The first thing that I would do is create a class to represent your record.
The second thing would be to get every line from your text file and iterate over them.
The third thing would be take the incoming information and convert it to your custom class which would be added to a List.
The last thing would be to then filter the List where Stores do not contain the letter "P".
Here is the code, in action:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Public Module Module1
Public Sub Main()
IO.File.WriteAllLines("test.txt", {
"Store - 001P",
"Owner - Greg",
"Price - 45000",
"Employees - 30",
"Store - 002",
"Owner - Jen",
"Price - 34400"
})
Dim lines() As String = IO.File.ReadAllLines("test.txt")
Dim stores As List(Of Store) = ConvertLinesToStores(lines)
Dim storesWithP() As Store = stores.Where(Function(s) s.StoreId.Contains("P")).ToArray()
Console.WriteLine("Stores with P: ")
Console.WriteLine(String.Join(Environment.NewLine, storesWithP.Select(Function(s) s.StoreId).ToArray()))
End Sub
Private Function ConvertLinesToStores(ByVal lines() As String) As List(Of Store)
Dim stores As New List(Of Store)
Dim item As Store
For Each line As String In lines
Dim parts() As String = line.Split(" - ")
If (lines.Length < 3) Then
Continue For
End If
Dim propertyName As String = parts(0)
Dim propertyValue As String = parts(2)
Dim employeeCount As Integer
If (propertyName = "Store") Then
If (item IsNot Nothing) Then
stores.Add(item)
End If
item = New Store() With {.StoreId = propertyValue}
ElseIf (propertyName = "Owner") Then
item.Owner = propertyValue
ElseIf (propertyName = "Price") Then
item.Price = propertyValue
ElseIf (propertyName = "Employees" AndAlso Integer.TryParse(propertyValue, employeeCount)) Then
item.EmployeeCount = employeeCount
End If
Next
If (item IsNot Nothing) Then
stores.Add(item)
End If
Return stores
End Function
End Module
Public Class Store
Public Property StoreId As String
Public Property Owner As String
Public Property Price As Integer
Public Property EmployeeCount As Integer
End Class
Fiddle: Live Demo
Assuming your text file has a consistent pattern the following will give you a list of your stores with all the properties.
My Stores.txt looks like this...
Store - 001P
Owner - Greg
Price - 45000
Employees - 30
Store - 002
Owner- Jen
Price - 34400
Employees - 20
Store - 03P
Owner - Mary
Price - 50000
Employees - 22
Store - 04P
Owner - Bill
Price - 42000
Employees - 90
I created a simple class to hold the store data.
Public Class Store
Public Property ID As String
Public Property Owner As String
Public Property Price As Decimal
Public Property Employees As Integer
End Class
First I read the file getting an array of lines. I loop through the lines of the file skipping ahead by 5 on each iteration (Step 5).This should hit the Store line (Store appears every 5 lines).
If it contains a P then I create a new Store and set it properties by moving down one line (the i + 1 etc.). After the properties are set, I add the Store to the list.
Now you have a List(Of Store) containing only the stores who's Id contains a P. You can access the items by index or in a For Each loop and have all the properties available.
Private StoreList As New List(Of Store)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lines = File.ReadAllLines("C:\Users\maryo\Desktop\Stores.txt")
For i = 0 To lines.Length - 4 Step 5
Dim ID = lines(i).Split("-"c)(1).Trim
If ID.Contains("P") Then
Dim st As New Store
st.ID = ID
st.Owner = lines(i + 1).Split("-"c)(1).Trim
st.Price = CDec(lines(i + 2).Split("-"c)(1).Trim)
st.Employees = CInt(lines(i + 3).Split("-"c)(1).Trim)
StoreList.Add(st)
End If
Next
'Just to check if my list contains what I expected.
DataGridView1.DataSource = StoreList
End Sub

Visual Basic: loaded parallel list boxes with text file substrings, but now items other than lstBox(0) "out of bounds"

The text file contains lines with the year followed by population like:
2016, 322690000
2015, 320220000
etc.
I separated the lines substrings to get all the years in a list box, and all the population amounts in a separate listbox, using the following code:
Dim strYearPop As String
Dim intYear As Integer
Dim intPop As Integer
strYearPop = popFile.ReadLine()
intYear = CInt(strYearPop.Substring(0, 4))
intPop = CInt(strYearPop.Substring(5))
lstYear.Items.Add(intYear)
lstPop.Items.Add(intPop)
Now I want to add the population amounts together, using the .Items to act as an array.
Dim intPop1 As Integer
intPop1 = lstPop.Items(0) + lstPop.Items(1)
But I get an error on lstPop.Items(1) and any item other than lstPop.Items(0), due to out of range. I understand the concept of out of range, but I thought that I create an index of several items (about 117 lines in the file, so the items indices should go up to 116) when I populated the list box.
How do i populate the list box in a way that creates an index of list box items (similar to an array)?
[I will treat this as an XY problem - please consider reading that after reading this answer.]
What you are missing is the separation of the data from the presentation of the data.
It is not a good idea to use controls to store data: they are meant to show the underlying data.
You could use two arrays for the data, one for the year and one for the population count, or you could use a Class which has properties of the year and the count. The latter is more sensible, as it ties the year and count together in one entity. You can then have a List of that Class to make a collection of the data, like this:
Option Infer On
Option Strict On
Imports System.IO
Public Class Form1
Public Class PopulationDatum
Property Year As Integer
Property Count As Integer
End Class
Function GetData(srcFile As String) As List(Of PopulationDatum)
Dim data As New List(Of PopulationDatum)
Using sr As New StreamReader(srcFile)
While Not sr.EndOfStream
Dim thisLine = sr.ReadLine
Dim parts = thisLine.Split(","c)
If parts.Count = 2 Then
data.Add(New PopulationDatum With {.Year = CInt(parts(0).Trim()), .Count = CInt(parts(1).Trim)})
End If
End While
End Using
Return data
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim srcFile = "C:\temp\PopulationData.txt"
Dim popData = GetData(srcFile)
Dim popTotal = 0
For Each p In popData
lstYear.Items.Add(p.Year)
lstPop.Items.Add(p.Count)
popTotal = popTotal + p.Count
Next
' popTotal now has the value of the sum of the populations
End Sub
End Class
If using a List(Of T) is too much, then just use the idea of separating the data from the user interface. It makes processing the data much simpler.

Visual Basic, new to coding. Need to make a list of 100 integers create a shuffled list of 70 results

I'm new to coding and I have got firmly stuck on this.
I've created a list in Visual Basic with
Dim integerStable As New List(Of Integer)()
integerStable.Add(0)
integerStable.Add(1)
integerStable.Add(2)
'through to integerStable.Add(99)
I'm trying to keep this list (it can be shuffled as long as all numbers stay in the list in general) and create a 2nd list where it only has 70 results from that shuffle.
I need that list, so I can call on it to perform some tasks for me later.
Can anyone help me work out how to do this? Remember I'm new to coding, but I'll try to follow along.
One of the most efficient ways to create your list would be as follows:
Dim integerStable As New List(Of Integer)
For i = 1 To 100
integerStable.Add(i)
Next
That at least should save you a lot of typing!!
You could also do the following:
Dim integerStable As New List(Of Integer)
Dim i As Integer
While i <= 100
integerStable.Add(i)
i += 1
End While
**Note though that the latter example will give you 101 items as integer is initially set to 0 **
You also need to remember that the list will be 'indexed' from 0 NOT 1 which is an important thing to remember when it comes to manipulating the items with it.
It can be much simpler.
Private Shared PRNG As New Random
' 100 numbers starting at zero, in random order
Private listOnum As List(Of Integer) = Enumerable.Range(0, 100).OrderBy(Function(x) PRNG.Next).ToList
' list of 70 numbers from list of 100
Private list70 As List(Of Integer) = listOnum.Take(70).ToList
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'did it work?
Dim ct As Integer = 1
For Each n As Integer In list70
Debug.WriteLine("{0}. {1,3}", ct, n)
ct += 1
Next
End Sub
Enumerable.Range takes two arguments. The first is a start number and the second is a count, so in the example it created a list that started at 0 and ended with 99, 100 items. The OrderBy just sorted that list by random numbers.
list70 is created by taking the first 70 items from listOnum.
The Random, PRNG, is created that way so that there is only ONE random that is only initialized once. You can find many problems associated with the incorrect initialization of Random.
edit: Slightly different approach.
Private Shared PRNG As New Random
' 100 numbers starting at zero
Private listOnum As List(Of Integer) = Enumerable.Range(0, 100).ToList
' list of 70 random numbers from list of 100
Private list70 As List(Of Integer) = listOnum.OrderBy(Function(x) PRNG.Next).Take(70).ToList
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'did it work?
Dim ct As Integer = 1
For Each n As Integer In list70
Debug.WriteLine("{0}. {1,3}", ct, n)
ct += 1
Next
'recreate list of 70
list70 = listOnum.OrderBy(Function(x) PRNG.Next).Take(70).ToList
End Sub