Visual Basic Text File - vb.net

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"

Related

VB.NET Index out of Range exception related to text file

I have some code I have used many times over which has always worked great for me. The latest use, however, throws an exception under certain circumstances that I cannot seem to resolve. Here it is:
I read from a text file to an array, use it as a binding source for some of my controls (it autofills 3 controls based on the selection of a single control). I created a Student class with 4 properties (Name, ID, DOB and DOE). Here is the code I use:
Private Sub autoFill()
Dim rost As String = "Roster.txt"
Dim lines As List(Of String) = File.ReadAllLines(rost).ToList
Dim list As List(Of Student) = New List(Of Student)
For i As Integer = 0 To lines.Count - 1
Dim data As String() = lines(i).Split(":")
list.Add(New Student() With {
.StudentName = data(0),
.StudentID = data(1),
.StudentDOB = data(2),
.StudentDOE = data(3)
})
Next
StudentBindingSource.DataSource = list
End Sub
Now here is the problem. In the "For" loop when I set i to 0 to lines.count -1 it throws this error:
VB>NET EXCEPTION
However...If I change i to 1 instead of 0 it works OR if I take away data(2) and data(3) it works with i = 0. I would prefer to use 0 so that I can have a blank line in the combobox or "--choose--", etc. The only thing I have thought that might be useful is that my first row in the text file has nothing to split. Here is the line format of the text file:
Student Name ID# DOB DOE <-----This header row is NOT in the text file
Last Name, First Name : 0000000 : 01/01/2021 : 01/01/2021
I'm going to assume I'm missing something really simple here. Any guidance would be greatly appreciated! Thank you.
Before we get to the actual problem, let's re-work some things.
A better way to structure code, especially when working with data loading, is to have a method that accepts an input and returns a result. Additionally, calling ToList() or ToArray() is a very expensive operation for performance. Very often you can improve performance dramatically by working with a lower-level IEnumerable for as long as possible.
With those principles in mind, consider this code:
Private Function ReadStudentData(fileName As String) As IEnumerable(Of Student)
Dim lines As IEnumerable(Of String) = File.ReadLines(fileName)
Return lines.
Select(Function(line) line.Split(":")).
Select(Function(data)
Return New Student() With {
.StudentName = data(0),
.StudentID = data(1),
.StudentDOB = data(2),
.StudentDOE = data(3)
}
End Function)
End Function
Private Sub autoFill()
StudentBindingSource.DataSource = ReadStudentData("Roster.txt")
End Sub
Now on to the actual issue. The problem was not from looping through the list variable. The problem is the data array. At some point you have a line that doesn't have enough elements. This is common, for example, as the last line in a file.
There are many ways to address this. In some cases, the exception is already the appropriate result, because if you have bad data you really don't want to continue. In other cases you want to log the bad records, perhaps to a report you can easily review later. Or maybe you just want to ignore the error, or pre-filter for rows with the right number of columns. Here is an example of the last option:
Private Function ReadStudentData(fileName As String) As IEnumerable(Of Student)
Return File.ReadLines(fileName).
Select(Function(line) line.Split(":")).
Where(Function(data) data.Length = 4).
Select(Function(data)
Return New Student() With {
.StudentName = data(0),
.StudentID = data(1),
.StudentDOB = data(2),
.StudentDOE = data(3)
}
End Function)
End Function
Private Sub autoFill()
StudentBindingSource.DataSource = ReadStudentData("Roster.txt")
End Sub
The problem is that you didn't check 'data' to have enough elements to create the 'Student'. A simple check should fix it.
Private Sub autoFill()
Dim rost As String = "Roster.txt"
Dim lines As List(Of String) = File.ReadAllLines(rost).ToList
Dim list As List(Of Student) = New List(Of Student)
For i As Integer = 0 To lines.Count - 1
Dim data As String() = lines(i).Split(":"c)
'Check data
If data.Length >= 4 Then '
list.Add(New Student() With {
.StudentName = data(0),
.StudentID = data(1),
.StudentDOB = data(2),
.StudentDOE = data(3)
})
End If
Next
StudentBindingSource.DataSource = list
End Sub
try this code:
Dim list As List(Of Student) = New List(Of Student)(100)
basically initialize the student list with a capacity. This is the capacity of the list, not the count/length.

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.)

(VB.NET) Find all duplicates in a list of objects based on multiple properties

I have a list of CommissionStatement objects which i created. I need to create a new list which only holds the duplicates found in this list based on 3 properties: Firm; Provider; and Total (ie each of these 3 have to be the same in 2 or more objects for it to be recognized as a duplicate)
Object is a simple object of strings at the moment.
Private Class CommissionStatement
Property Provider As String
Property Firm As String
Property Source As String
Property Media As String
Property Total As String
Property Received As String
End Class
I have a list of all CommissionStatments as follows:
Dim fileLocation As String = importText.Text
Dim csvText As String = My.Computer.FileSystem.ReadAllText(fileLocation).Replace(", ", " ")
Dim providerString As String = ""
Dim allStatements = New List(Of CommissionStatement)
Dim countIndex As Integer = 0, maxIndex As Integer = csvText.Split(vbLf).Length
For Each line As String In csvText.Split(vbLf)
'' Remove the top row
countIndex += 1
If countIndex = 1 Then
Continue For
End If
statementProgress.Value = ((countIndex / maxIndex) * 100)
'' Build the New commissionStatement object and add it to the allStatements list
If Not line = "" Then
Dim commissionStatement = New CommissionStatement
With commissionStatement
.Provider = line.Split(",")(0)
.Firm = line.Split(",")(1)
.Source = line.Split(",")(2)
.Media = line.Split(",")(3)
.Total = line.Split(",")(4)
End With
providerString &= commissionStatement.Provider & ","
allStatements.Add(commissionStatement)
End If
Next
First post on StackOverflow so sorry if its not very clear! The duplicate list needs to also be a list of CommissionStatements which contain the duplicates from the allStatements list based on Firm Provider and Total
Your best bet is to use a lambda expression. The function below should do what you ask.
Private Function GetDuplicateCommisionStatements(tempStatement As CommissionStatement) As List(Of CommissionStatement)
Return allStatements.FindAll(Function(x) x.Firm = tempStatement.Firm And x.Provider = tempStatement.Provider And x.Total = tempStatement.Total)
End Function
And use it like this..
duplicatelist = GetDuplicateCommisionStatements(testCommisionStatement)
using your own object names of course.
Incidentally, you could shorten the sub using the With statement like below
Private Function GetDuplicateCommisionStatements(tempStatement As CommissionStatement) As List(Of CommissionStatement)
With tempStatement
Return allStatements.FindAll(Function(x) x.Firm = .Firm And x.Provider = .Provider And x.Total = .Total)
End With
End Function

Merge multiple list of string to list of object using VB.NET

I have 3 list of strings.
List1 - Student Name List2 - Student School List3 - Student Location
Student 1 Student 1 School Student 1 Location
Student 2 Student 2 School Student 2 Location
Student 3 Student 3 School Student 3 Location
Student 4 Student 4 School Student 4 Location
Student 5 Student 5 School Student 5 Location
And a structure StudentDetails
Public Structure StudentDetails()
Public StudentName As String
Public StudentSchool As String
Public StudentLocation As String
End Structure
I want to make the first 3 list to List of StudentDetails
I have used the following code to do this
Dim StudentDetailsList As New List(Of StudentDetails)
For i = 0 to List1.Count - 1
Dim StudentDetail As New StudentDetail
With StudentDetail
.StudentName = List1(i)
.StudentSchool = List2(i)
.StudentLocation = List3(i)
End With
StudentDetailsList.Add(StudentDetail)
Next
Is there a better way to do this using Linq or some other method?
There are many ways to do that, some easier to read than others.
First, I would make StudentDetails a class instead of a structure (see, e.g., When should I use a struct instead of a class?.
Now that you have a class, you can give it a New constructor with parameters, as used in the third example here:
Option Infer On
Option Strict On
Module Module1
Public Class StudentDetails
Public Name As String
Public School As String
Public Location As String
Public Sub New()
' empty constuctor
End Sub
Public Sub New(name As String, school As String, location As String)
Me.Name = name
Me.School = school
Me.Location = location
End Sub
' make it easy to represent StudentDetails as a string...
Public Overrides Function ToString() As String
Return $"{Me.Name} {Me.School} {Me.Location}"
End Function
End Class
Sub Main()
Dim list1 As New List(Of String) From {"Adam", "Betty", "Charles", "Wilma"}
Dim list2 As New List(Of String) From {"Ace", "Best", "Classy", "Wacky"}
Dim list3 As New List(Of String) From {"Attic", "Basement", "Cellar", "Windowledge"}
' a not-very tidy example using Zip:
Dim StudentDetailsList = list1.Zip(list2, Function(a, b) New With {.name = a, .school = b}).Zip(list3, Function(c, d) New StudentDetails With {.Name = c.name, .School = c.school, .Location = d}).ToList()
' one way of writing out the StudentDetailsList...
For Each s In StudentDetailsList
Console.WriteLine(s.ToString())
Next
StudentDetailsList.Clear()
' a bit cleaner using a loop:
For i = 0 To list1.Count() - 1
StudentDetailsList.Add(New StudentDetails With {
.Name = list1(i),
.School = list2(i),
.Location = list3(i)})
Next
' another way of writing out the StudentDetailsList...
Console.WriteLine(String.Join(vbCrLf, StudentDetailsList))
StudentDetailsList.Clear()
' easy to write with a New constructor, but not necessarily as easy to read as the previous example:
For i = 0 To list1.Count() - 1
StudentDetailsList.Add(New StudentDetails(list1(i), list2(i), list3(i)))
Next
Console.WriteLine(String.Join(vbCrLf, StudentDetailsList))
Console.ReadLine()
End Sub
End Module
I used the $ string formatter in the .ToString() method: it was introduced with VS2015, so if you are using an earlier version you can use String.Format("{0} {1} {2}", Me.Name, Me.School, Me.Location) instead.
As a note on naming the properties of StudentDetails, the "Student" in StudentName, StudentSchool and StudentLocation is redundant.
Well, you can use an overload of Select extension method to projects each element of one of the list into a new StudentDetail by incorporating the element's index. Assuming the three lists have the same amount of elements, you can do the following:
// using C#
var result=List1.Select((e,i))=>new StudentDetail
{
StudentName =e,
StudentSchool = List2[i],
StudentLocation = List3[i]
}).ToList();
I think in Vb would be (sorry, I'm a c# programmer):
Dim StudentDetailsList=List1.Select(Function(e, i) _
New StudentDetail
With StudentDetail
.StudentName = e
.StudentSchool = List2(i)
.StudentLocation = List3(i)
End With).ToList();
But using a for is not a bad solution, in many cases is more readable.

Visual Basic LINQ

I have two files one with senators who are retiring and one of complete senators. I need to remove the retiring senators from the complete list. I am using LINQ for this bu itt is not working as i would expect it to. I am trying to compare one query to another only take the senator out of the current list who are not retiring and then add them into a new file. But when i open the new file it has not removed the retired senators. Here is my code thanks for any help in advance.
' Query for retired senators
Dim senateRetiered = From line In retieredSen
Let data = line.Split(","c)
Let retName = data(0)
Select retName
' query length
Dim index As Integer = senateRetiered.ToArray.Length
' Add one to our index we will remove it later in our next query
index += 1
' Query for or 110th senate compare to our other query and only select senators that have not retiered
Dim senate110Query = From line In senate110
Let data = line.Split(","c)
Let name = data(0)
Let state = data(1)
Let party = data(2)
Where name <> senateRetiered(index - 1)
Select line
' Write the remaining sentators to a new file
IO.File.WriteAllLines("sortedSentate.txt", senate110Query)
I'm not sure I really understood your question, but I think the second query should look more like that:
Dim senate110Query = From line In senate110
Let data = line.Split(","c)
Let name = data(0)
Let state = data(1)
Let party = data(2)
Where Not senateRetired.Any(Function(r) r.Name = name)
Select line
I have an alternative solution which is somewhat more complicated; however, it yields the senators as class. If you need to do some other work with senators in addition to file creation it could be useful
Class Senator
Public Name As String
Public State As String
Public Party As String
Public Overrides Function Equals(ByVal obj As Object) As Boolean
Dim other = TryCast(obj, Senator)
If other Is Nothing Then
Return False
End If
Return Name.Equals(other.Name)
End Function
Public Overrides Function ToString() As String
Return String.Format("{0},{1},{2}", Name, State, Party)
End Function
End Class
With this declaration you can do this
Dim senateRetiered = From line In retieredSen _
Let data = line.Split(","c) _
Select New Senator() With {.Name = data(0)}
Dim senate110Query = From line In senate110 _
Let data = line.Split(","c) _
Select New Senator() With {.Name = data(0), .State = data(1), .Party = data(2)}
Dim nonRetired = senate110Query.Except(senateRetiered)
Dim nonRetiredString = nonRetired _
.Select(Function(sen) sen.ToString())
'Note: I have to append a ".ToArray()" with .NET 3.5
IO.File.WriteAllLines("sortedSentate.txt", nonRetiredString)