The objective of the program is to interpret hockey statistics from a file using StreamReader and then display an added column of points. The following code kinda does so, however it’s ineffective in the sense that it doesn’t add the points value to the array - it separately outputs it. Looking for assistance as to how it would be possible to incorporate the points value into aryTextFile();
Dim hockeyFile, LineOfText, aryTextFile() As String
Dim i As Integer
Dim nameText(), NumberText(), goalsText(), assistsText(), GamesWonText() As String
Dim IntAssists(), IntGoals(), PointsText() As Single
hockeyFile = "C:\Users\Bob\Downloads\hockey.txt" 'state location of file
Dim objReader As New System.IO.StreamReader(hockeyFile) 'objReader can read hockeyFile
For i = 0 To objReader.Peek() <> -1 'reads each line seperately, ends when there is no more data to read
LineOfText = objReader.ReadLine 'stores seperate lines of data in HockeyFile into LineofText
aryTextFile = LineOfText.Split(",") 'takes lines and converts data into array
Name = aryTextFile(0) 'first piece of data in lines of text is the name
nameText(i) = aryTextFile(0)
If nameText(0) = "Name" Then
TextBox1.Text = LineOfText & ", Points." & vbCrLf 'displays first line fo text and adds "Points" label
End If
If Name <> "Name" Then 'when second line starts, then begin to intepret data
NumberText(i) = aryTextFile(1)
assistsText(i) = aryTextFile(2) 'assists are in third value of array
goalsText(i) = aryTextFile(3) 'goals are in fourth value of array
GamesWonText(i) = aryTextFile(4)
IntAssists(i) = Val(assistsText(i)) 'since our assists value is a string by default, it must be converted to a integer
IntGoals(i) = Val(goalsText(i)) 'since our goals value is a string by default, it must be converted to a integer
PointsText(i) = (IntGoals(i) * 2) + (IntAssists(i)) 'goals are two points, assists are one point
TextBox1.Text = TextBox1.Text & NumberText(i) & assistsText(i) & goalsText(i) & GamesWonText(i) & PointsText(i) & vbCrLf 'Displays points as last value in each line
End If
Next i
This should get you pretty close:
It'll need extra validation. It doesn't take into account whatever value you have between the name and the goals.
Private Sub ProcessHockeyStats()
Try
Dim inputFile As String = "c:\temp\hockey.txt"
Dim outputFile As String = "c:\temp\output.txt"
If Not File.Exists(inputFile) Then
MessageBox.Show("Missing input file")
Return
End If
If File.Exists(outputFile) Then
File.Delete(outputFile)
End If
Dim lines() As String = File.ReadAllLines(inputFile)
Dim output As List(Of String) = New List(Of String)
Dim firstLine As Boolean = True
For Each line As String In lines
Dim values() As String = line.Split(","c)
Dim points As Integer
If firstLine Then
output.Add("Name, Assists, Goals, Points")
firstLine = False
Else
'needs validation for values
points = CInt(values(1) * 2) + CInt(values(2))
output.Add(String.Concat(line, ",", points))
End If
Next
File.WriteAllLines("c:\temp\outfile.txt", output)
Catch ex As Exception
MessageBox.Show(String.Concat("Error occurred: ", ex.Message))
End Try
End Sub
VS2008 is ancient, especially when later versions of Visual Studio are free. I felt like showing an implementation using more-recent code. Like others, I strongly support building a class for this. The difference is my class is a little smarter, using the Factory pattern for creating instances and a Property to compute Points as needed:
Public Class HockeyPlayer
Public Property Name As String
Public Property Number As String
Public Property Assists As Integer
Public Property Goals As Integer
Public Property Wins As Integer
Public ReadOnly Property Points As Integer
Get
Return (Goals * 2) + Assists
End Get
End Property
Public Shared Function FromCSVLine(line As String) As HockeyPlayer
Dim parts() As String = line.Split(",")
Return New HockeyPlayer With {
.Name = parts(0),
.Number = parts(1),
.Assists = CInt(parts(2)),
.Goals = CInt(parts(3)),
.Wins = CInt(parts(4))
}
End Function
End Class
Dim hockeyFile As String = "C:\Users\Bob\Downloads\hockey.txt"
Dim players = File.ReadLines(hockeyFile).Skip(1).
Select(Function(line) HockeyPlayer.FromCSVLine(line)).
ToList() 'ToList() is optional, but I included it since you asked about an array
Dim result As New StringBuilder("Name, Number, Assists, Goals, Wins, Points")
For Each player In players
result.AppendLine($"{player.Name}, {player.Number}, {player.Assists}, {player.Goals}, {player.Wins}, {player.Points}")
Next player
TextBox1.Text = result.ToString()
I was gonna give you VS 2008 version afterward, but looking at this, the only thing here you couldn't do already even by VS 2010 was string interpolation... you really should upgrade.
Parallel arrays are really not the way to handle this. Create a class or structure to organize the data. Then create a list of the class. The list can be set as the DataSource of a DataGridView which will display your data in nice columns with headings matching the names of your properties in the Hockey class. You can easily order your data in the HockeyList by any of the properties of Hockey.
Public Class Hockey
Public Property Name As String
Public Property Number As String
Public Property Goals As Integer
Public Property Assists As Integer
Public Property Points As Integer
Public Property GamesWon As Integer
End Class
Private HockeyList As New List(Of Hockey)
Private Sub FillListAndDisplay()
Dim path = "C:\Users\Bob\Downloads\hockey.txt"
Dim Lines() = File.ReadAllLines(path)
For Each line As String In Lines
Dim arr() = line.Split(","c)
Dim h As New Hockey()
h.Name = arr(0)
h.Number = arr(1)
h.Assists = CInt(arr(2).Trim)
h.Goals = CInt(arr(3).Trim)
h.GamesWon = CInt(arr(4).Trim)
h.Points = h.Goals * 2 + h.Assists
HockeyList.Add(h)
Next
Dim orderedList = (From scorer In HockeyList Order By scorer.Points Ascending Select scorer).ToList
DataGridView1.DataSource = orderedList
End Sub
Related
I am trying to stay ahead of my Year 12 Software class. Starting to work with records and arrays. I have answered the question, but the solution feels very clunky. I am hoping someone has suggestions/links for completing this task in a more efficient way.
The task: read in lines from a text file and into a structure, and then loop through that, populating four list boxes if an animal hasn't been vaccinated.
Here's the code:
Imports System.IO
Public Class Form1
'Set up the variables - customer record, total pets not vaccinated, total records in the file, and a streamreader for the file.
Structure PetsRecord
Dim custName As String
Dim address As String
Dim petType As String
Dim vacced As String
End Structure
Dim totNotVac As Integer
Dim totalRecCount As Integer
Dim PetFile As IO.StreamReader
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
'set an array of records to store each record as it comes in. Limitation: you need to know how many records in the file. Set the array at 15 to allow for adding more in later.
Dim PetArray(15) As PetsRecord
'variables that let me read in a line and split it into sections.
Dim lineoftext As String
Dim i As Integer
Dim arytextfile() As String
'tell them what text file to read
PetFile = New IO.StreamReader("patients.txt")
totNotVac = 0
Try
totalRecCount = 0
' read each line in and split the lines into fields for the records. Then assign the fields from the array. Finally, reset the array and loop.
Do Until PetFile.Peek = -1
'read in a line of text
lineoftext = PetFile.ReadLine()
'split that line into bits separated by commas. these will go into the array.
arytextfile = lineoftext.Split(",")
'dunno whether this is the best way to do it, but stick the array bits into the record, and then clear the array to start again.
PetArray(totalRecCount).custName = arytextfile(0)
PetArray(totalRecCount).address = arytextfile(1)
PetArray(totalRecCount).petType = arytextfile(2)
PetArray(totalRecCount).vacced = arytextfile(3)
totalRecCount += 1
Array.Clear(arytextfile, 0, arytextfile.Length)
Loop
For i = 0 To PetArray.GetUpperBound(0)
If PetArray(i).vacced = "No" Then
lstVaccinated.Items.Add(PetArray(i).vacced)
lstCustomer.Items.Add(PetArray(i).custName)
lstAddress.Items.Add(PetArray(i).address)
lstPetType.Items.Add(PetArray(i).petType)
totNotVac += 1
lblVacTotal.Text = "The total number of unvaccinated animals is " & CStr(totNotVac)
End If
Next
Catch ex As Exception
MsgBox("Something went wrong with the file")
End Try
PetFile.Close()
End Sub
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
Close()
End Sub
End Class
And one line from the patient.txt file:
Richard Gere,16 Sunset Blvd,Gerbil,No
I hope this isn't out of place.
Regards,
Damian
If you want to use Streams be aware that they need to be disposed.
File.ReadAllLines returns an array of lines. Since the array is initialized where it is declared we don't need to specify a size.
If you use List(Of T) you do not have to know in advance the number of elements in the list. Avoids the limitation of array.
Using a For Each avoids having to use the size. The small c following "," tells the compiler that this is a Char which is what .Split is expecting. If you had Option Strict On, which you always should, you would have seen an error. You add items to the list by creating a New PetsRecord. The parametrized Sub New receives the values and sets the properties.
Don't change the display in the label on each iteration. Use an interpolated string (preceded by a $) which allows embedded variables surrounded by braces { }. It is not necessary to change the number to a string as it is implied by the interpolator. (Available in VS2015 and later)
Public Structure PetsRecord
Public Property custName As String
Public Property address As String
Public Property petType As String
Public Property vacced As String
Public Sub New(name As String, add As String, pType As String, vac As String)
custName = name
address = add
petType = pType
vacced = vac
End Sub
End Structure
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
Dim lines = File.ReadAllLines("patients.txt")
Dim lstPet As New List(Of PetsRecord)
For Each line In lines
Dim splits = line.Split(","c)
lstPet.Add(New PetsRecord(splits(0), splits(1), splits(2), splits(3)))
Next
Dim totNotVac As Integer
For Each pet In lstPet
If pet.vacced = "No" Then
lstVaccinated.Items.Add(pet.vacced)
lstCustomer.Items.Add(pet.custName)
lstAddress.Items.Add(pet.address)
lstPetType.Items.Add(pet.petType)
totNotVac += 1
End If
Next
lblVacTotal.Text = $"The total number of unvaccinated animals is {totNotVac}"
End Sub
If you don't need the 'PetsRecord' array to store data, take a look at the following code:
Dim totNotVac As Integer
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
File.ReadAllLines("your text file path").ToList().ForEach(Sub(x)
lstVaccinated.Items.Add(x.Split(","c)(0))
lstCustomer.Items.Add(x.Split(","c)(1))
lstAddress.Items.Add(x.Split(","c)(2))
lstPetType.Items.Add(x.Split(","c)(3))
totNotVac += 1
End Sub)
lblVacTotal.Text = "The total number of unvaccinated animals is " & CStr(totNotVac)
End Sub
I have to make a application that organizes a list of runners and their teams. In the following text file, I have to remove the top half of the text file (the top half being the listed teams) and display only the bottom half (the runners)in a listbox item.
The Text file:
# School [School Code|School Name|Coach F-Name|Coach L-Name|AD F-Name|AD L Name]
WSHS|Worcester South High School|Glenn|Clauss|Bret|Zane
WDHS|Worcester Dorehty High School|Ellsworth|Quackenbush|Bert|Coco
WBCHS|Worcester Burncoat High School|Gail|Cain|Kevin|Kane
QRHS|Quabbin Regional High School|Bob|Desilets|Seth|Desilets
GHS|Gardner High School|Jack|Smith|George|Fanning
NBHS|North Brookfield High School|Hughe|Fitch|Richard|Carey
WHS|Winchendon High School|Bill|Nice|Sam|Adams
AUBHS|Auburn High School|Katie|Right|Alice|Wonderland
OXHS|Oxford High School|Mary|Cousin|Frank|Daughter
# Roster [Bib #|School Code|Runner's F-Name|Runner's L-Name]
101|WSHS|Sanora|Hibshman
102|WSHS|Bridgette|Moffitt
103|WSHS|Karine|Chunn
104|WSHS|Shanita|Wind
105|WSHS|Fernanda|Parsell
106|WSHS|Albertha|Baringer
107|WSHS|Carlee|Sowards
108|WDHS|Maisha|Kleis
109|WDHS|Lezlie|Berson
110|WDHS|Deane|Rocheleau
111|WDHS|Hang|Hodapp
112|WDHS|Zola|Dorrough
113|WDHS|Shalon|Mcmonigle
I have some code that reads each row from the text file as an array and uses boolean variables to determine where to end the text file. This worked with displaying only the teams, which I've managed to do. But I now need to do the opposite and display only the players, and I'm a bit stumped.
My Code:
Private Sub btnLoadTeams_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLoadTeam.Click
' This routine loads the lstTeam box from an ASCII .txt file
' # School [School Code | Name | Coach F-Name| Coach L-Name | AD F-Name | AD L-Name]
Dim strRow As String
Dim bolFoundCode As Boolean = False
Dim bolEndCode As Boolean = False
Dim bolFoundDup As Boolean = False
Dim intPosition As Integer
Dim intPosition2 As Integer
Dim strTeamCodeIn As String
Dim textIn As New StreamReader( _
New FileStream(txtFilePath.Text, FileMode.OpenOrCreate, FileAccess.Read))
' Clear Team listbox
lstTeam.Items.Clear()
btnDeleteRunner.Enabled = True
Do While textIn.Peek <> -1 And Not bolEndCode
Me.Refresh()
strRow = textIn.ReadLine.Trim
If Not bolFoundCode Then
If "# SCHOOL " = UCase(Mid(strRow, 1, 9)) Then
bolFoundCode = True
End If
Else
If Mid(strRow, 1, 2) <> "# " Then
For Each item As String In lstTeam.Items
intPosition = InStr(1, strRow, "|")
strTeamCodeIn = Mid(strRow, 1, intPosition - 1)
intPosition2 = InStr(1, item, strTeamCodeIn)
If intPosition2 > 0 Then
bolFoundDup = True
MsgBox("Found Duplicate School Code: " & strTeamCodeIn)
End If
Else
bolEndCode = True
Next
If Not bolFoundDup Then
lstTeam.Items.Add(strRow)
Else
lstTeam.Items.Add("DUPLICATE School Code: " & strRow)
lstTeam.Items.Add("Please correct input file and reload teams")
bolEndCode = True
End If
End If
End If
Loop
End Sub
Ive put bolEndCode = True in between the part that reads the mid section of the text file, but all Ive managed to display is the following in the listbox:
# Roster [Bib #|School Code|Runner's F-Name|Runner's L-Name]
Any help or hints on how I would display just the runners to my "lstPlayers" listbox would be greatly appreciated. I'm a beginner programmer and We've only just started learning about reading and writing arrays in my .NET class.
First I made 2 classes, one Runner and one School. These have the properties available in the text file. As part of the class I added a function that overrides .ToString. This is for he list boxes that call .ToString for display.
Next I made a function that reads all the data in the file. This is very simple with the File.ReadLines method.
Then I created 2 variables List(Of T) T stands for Type. Ours Types are Runner and School. I used List(Of T) instead of arrays because I don't have to worry about what the size of the list is. No ReDim Preserve, just keep adding items. The FillList method adds the data to the lists. First I had to find where the schools ended and the runners started. I used the Array.FindIndex method which is a bit different because the second parameter is a predicate. Check it out a bit. Now we know the indexes of the lines we want to use for each list and use a For...Next loop. In each loop an instance of the class is created and the properties set. Finally the new object is added to the the list.
Finally we fill the list boxes with a simple .AddRange and the lists.ToArray. Note that we are adding the entire object, properties and all. The neat thing is we can access the properties from the listbox items. Check out the SelectedIndexChanged event. You can do the same thing with the Runner list box.
Sorry, I couldn't just work with your code. I have all but forgotten the old vb6 methods. InStr, Mid etc. It is better when you can to use .net methods. It makes your code more portable when the boss says "Rewrite the whole application in C#"
Public Class Runner
Public Property BibNum As Integer
Public Property SchoolCode As String
Public Property FirstName As String
Public Property LastName As String
Public Overrides Function ToString() As String
'The listbox will call .ToString when we add a Runner object to determin what to display
Return $"{FirstName} {LastName}" 'or $"{LastName}, {FirstName}"
End Function
End Class
Public Class School
Public Property Code As String
Public Property Name As String
Public Property CoachFName As String
Public Property CoachLName As String
Public Property ADFName As String
Public Property ADLName As String
'The listbox will call .ToString when we add a School object to determin what to display
Public Overrides Function ToString() As String
Return Name
End Function
End Class
Private Runners As New List(Of Runner)
Private Schools As New List(Of School)
Private Function ReadData(path As String) As String()
Dim lines = File.ReadLines(path).ToArray
Return lines
End Function
Private Sub FillLists(data As String())
Dim location = Array.FindIndex(data, AddressOf FindRosterLine)
'The first line is the title so we don't start at zero
For index = 1 To location - 1
Dim SplitData = data(index).Split("|"c)
Dim Schl As New School
Schl.Code = SplitData(0)
Schl.Name = SplitData(1)
Schl.CoachFName = SplitData(2)
Schl.CoachLName = SplitData(3)
Schl.ADFName = SplitData(4)
Schl.ADLName = SplitData(5)
Schools.Add(Schl)
Next
For index = location + 1 To data.GetUpperBound(0)
Dim SplitData = data(index).Split("|"c)
Dim Run As New Runner
Run.BibNum = CInt(SplitData(0))
Run.SchoolCode = SplitData(1)
Run.FirstName = SplitData(2)
Run.LastName = SplitData(3)
Runners.Add(Run)
Next
End Sub
Private Function FindRosterLine(s As String) As Boolean
If s.Trim.StartsWith("# Roster") Then
Return True
Else
Return False
End If
End Function
Private Sub FillListBoxes()
Dim arrRunners As Runner() = Runners.ToArray
Dim arrSchools As School() = Schools.ToArray
ListBox1.Items.AddRange(arrSchools)
ListBox2.Items.AddRange(arrRunners)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim arrRunner = ReadData("Runners.txt")
FillLists(arrRunner)
FillListBoxes()
End Sub
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
Dim Schl = DirectCast(ListBox1.SelectedItem, School)
TextBox1.Text = Schl.CoachLName
TextBox2.Text = Schl.Code
End Sub
I have multiple text files with similar data, for instance file1 would have the following entry:
test1, 1400
Then file2 would have:
test1, 2400
As all of these files are generated by different programs, is it possible to check through each text file for a similar entry, for instance using the files mentioned above, say I wanted to find test1 from both files and calculate the sum of the score and thus get the following data saved to another text file:
test1, 3800
The Programming Language I am using is VB.NET, currently I have read all of the files using:
Dim Array1() As String = IO.File.ReadAllLines("path")
My current logic is to get all of the data into a list or a KeyValuePair, where the list will store the username of the user as well with their score which would be summed at this point. I have currently read all of the data from each text file to an array, with each array in the FormLoad Event I have got it into a form where the data is split with a delimiter with the comma. At the start of the program I have an Input Box which asks the user for their Username and stores it in a variable called UserInput. From there this is what I need help achieving, I need the Program to get value from each array and store it in another array where it sorts the data of each user with their scores, from their I can use: For i = 0 to ar.length - 1 Loop to go through the array and search for the Users username.
You can use the following approach
Dim arr1 As New List(Of String)
arr1.AddRange(IO.File.ReadAllLines("text file 1"))
Dim arr2 As New List(Of String)
arr2.AddRange(IO.File.ReadAllLines("text file 2"))
Dim searchstring As String = "test1"
'You can replace test1 with the string you are searching the text file for
Dim index1 As Integer = 0
Dim index2 As Integer = 0
'Getting the index of the string in the list
'*******************************************
For x As Integer = 0 To arr1.Items.Count - 1
If arr1(x).StartsWith(searchstring) Then
index1 = x
End If
Next
For x As Integer = 0 To arr2.Items.Count - 1
If arr2(x).StartsWith(searchstring) Then
index2 = x
End If
Next
'*******************************************
Dim split1() As String = Split(arr1(index1), ",")
Dim split2() As String = Split(arr2(index2), ",")
Dim sum As Integer = Integer.Parse(Trim(split1(1))) + Integer.Parse(Trim(split2(1)))
'Writing the sum to another test file, the "output.txt" file would be created on your desktop, you can replace the path's string with your custom location
Dim path As String = Path.Combine(My.Computer.FileSystem.SpecialDirectories.Desktop, "output.txt")
Dim finaltext As String = searchstring + "," + sum.ToString
System.IO.File.AppendAllLines(path, finaltext)
The above method creates a new text file "output.txt" on your desktop.
AJD is correct. You need to make an attempt before posting. I answered because I want practice with Linq. Since you have 2 different types of data, a class or a structure is in order (or use a database). I filled a list of the structure by splitting the lines into the string portion and integer portion; setting the properties of the structure. You can add as many files as you wish to the List. I tested with both the Linq method and the Loop method.
Structure TestData
Public TestName As String
Public TestScore As Integer
End Structure
Private TestList As New List(Of TestData)
Private Sub AddToList(path As String)
Dim arr() As String = File.ReadAllLines(path)
For Each line As String In arr
Dim arr2() As String = line.Split(","c)
Dim td As TestData
td.TestName = arr2(0)
td.TestScore = CInt(arr2(1).Trim)
TestList.Add(td)
Next
End Sub
Private Function GetSummWithLinq(NameOfTest As String) As Integer
Dim result As Integer = (From Score In TestList
Where Score.TestName = NameOfTest
Select Score.TestScore).Sum
Return result
End Function
Private Function GetSumWithLoop(NameOfTest As String) As Integer
Dim total As Integer
For Each item In TestList
If item.TestName = NameOfTest Then
total += item.TestScore
End If
Next
Return total
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim p As String = "C:\Users\SomeUser\Desktop\TestData.txt"
AddToList(p)
Dim a As Integer = GetSummWithLinq("test1")
MessageBox.Show($"The Linq method produces {a}")
Dim b As Integer = GetSumWithLoop("test1")
MessageBox.Show($"The Loop method produces {b}")
End Sub
EDIT
My TestData.txt file that I used to test the code.
test1, 314
test2, 740
test1, 700
test2, 200
I'm using VB.NET 2010 and have been trying to wrap my head around implementing a search feature using a sequential file. Originally I was trying to use a sequential file to read from and search but that has proven to be extremely difficult so I've taken a simple approach using two different arrays with data and try to search through Artist and Album.
The code below takes input from txtSearch.Text which will be for search by artist. I have a duplicate artist "TeeBee" but when I search for that artist I only receive one result instead of two as there are two different albums under the artist "TeeBee".
I thought about adding another loop but it doesn't work. I also thought that the results were getting cut off as there isn't a way to add a return to the results to continue on.
I'm a beginner to programming so please keep that in mind.
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
' Artist array
Dim strArtist() As String = {"Dillinja", "TeeBee", "Dieselboy", "TeeBee"}
' Album array
Dim strAlbum() As String = {"Untitled", "Scorpion", "Horns", "Blades"}
Dim strSearchForArtist As String
Dim intSub As Integer
' artist search
strSearchForArtist = txtSearch.Text
Do Until intSub = strAlbum.Length OrElse
strSearchForArtist = strArtist(intSub)
intSub = intSub + 1
Loop
If intSub < strArtist.Length Then
lstLibrary.Items.Add(strArtist(intSub) & " " & strAlbum(intSub) & vbNewLine)
Else
MessageBox.Show("Invalid", "Bad", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End Sub
Attempt to load text file into array but it doesn't create separate arrays like album, artist genre. - 11-19-14
Private Sub btnLoadArray_Click(sender As Object, e As EventArgs) Handles btnLoadArray.Click
Dim filePath As String = "library.txt"
Dim sr As IO.StreamReader
sr = IO.File.OpenText(filePath)
' look inside file and read every line
' this will be how we put the number for
' our array below
Dim TotalLines As Integer = 0
Dim word As String = ""
' need 3 to be dynamic so we get all lines in the file to build the array words(#)
' wanted to use words(,) but that does not work
Dim words() As String = IO.File.ReadAllLines(filePath)
Dim i As Integer = 0
' when the peak value is -1 we're at the end of the file
Do Until sr.Peek = -1
' load one at a time
word = sr.ReadLine()
' load word into array
words(i) = word
' output
lstArtist.Items.Add(words(i))
' increment counter
i += 1
Loop
'close the file
sr.Close()
End Sub
Well, instead of using arrays and handle all the complications I really suggest you to have a more object oriented approach
So start defining a class to keep the info for an Album like this
Public Class Album
Public ArtistName as String
Public AlbumTitle as String
Public Function ToString() as String
return ArtistName & " - " & AlbumTitle
End Function
End Class
Now you could remove the array mess using a List(Of Album) and asking to each item of this list to render its content via the ToString method.
Also an important role here is reserved to the IEnumerable function Where that extract from the List(Of Album) all the elements that respects the Lambda expression required by the Where method.
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
' Here the list is fixed but you can easily build it loading data from a database
' or from some other storage medium like a file etc...
Dim albumList = new List(Of Album) From _
{
new Album With { .ArtistName = "Dillinja", .AlbumTitle = "Untitled" },
new Album With { .ArtistName = "TeeBee", .AlbumTitle = "Scorpion"},
new Album With { .ArtistName = "Dieselboy", .AlbumTitle = "Horns" },
new Album With { .ArtistName = "TeeBee", .AlbumTitle = "Blades" }
}
' To help search you could integrate the ToLower expressions in
' Plutonix answer here...
Dim searchTerm = txtSearch.Text
Dim searchResult = albumList.Where(Function(x) x.ArtistName = searchTerm)
lstLibrary.Items.Clear()
if searchResult.Count > 0 Then
For Each item in searchResult
lstLibrary.Items.Add(item.ToString())
Next
Else
MessageBox.Show("Not found")
End if
End Sub
Rather than arrays, a List(of String) is easier to manage and provides more efficiency and the kind of functions you probably want.
Private Artists As New List(of String)
....
Artists.Addrange({"Dillinja", "TeeBee", "Dieselboy", "TeeBee"})
...
' find a single item:
If Artists.Contains(txtSearch.Text) Then ' no looping required
' a dupe
Else
' not a dupe
End If
To get all the matching items:
Dim find = Artists.Where(Function(s) _
s.ToLowerInvariant = txtSearch.Text.ToLowerInvariant).ToList
The resulting find will also be a List(of String) containing the matching item. This time it is case insensitive. To get the count of dupes:
Dim finds = Artists.LongCount(Function(n) n.ToLowerInvariant = _
txtSearch.Text.ToLowerInvariant.ToLowerInvariant)
I dont know how much value either can be. Neither 2 nor a List of "Teebee", "TeeBee" is very useful or interesting. More typically, you want the return to be the entire object (like an album) associated with the search term. This requires a Class which glues Arist, Album, Genere, Tracks etc together though (see Steve's answer for a start on this part).
As for the whole file part, the list(s) can be easily serialized allowing an entire list to be saved or reloaded from disk in 2-3 lines of code.
I have been developing a quiz application that uses a textfile to store questions.
The questions are formatted in this format "QUESTION##CHOICE_A##CHOICE_B##CHOICE_C##CHOICE_D##ANSWER"
I want it to read each line splits it into 6 different parts using "##" as the split string and store it into arrays such as Questions, CHOICE_A,CHOICE_B,CHOICE_C,CHOICE_D
My code does not loop. it just stores the first line
A graphical illustration is shown below of the questions
Here is my code
Dim sr As StringReader = New StringReader(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
Dim questions As String
Dim splitquestions(6) As String
Dim Unsplitquestions(6) As String
Dim i As Integer = 0
Do Until sr.Peek = -1
questions = sr.ReadLine
Unsplitquestions(i) = questions
splitquestions = Unsplitquestions(i).Split(New String() {"##"}, StringSplitOptions.RemoveEmptyEntries)
' Splits and Stores Into Various
'
'
globalVariables.ArrayQuestions.Add(splitquestions(0))
globalVariables.optionA.Add(splitquestions(1))
globalVariables.optionB.Add(splitquestions(2))
globalVariables.optionC.Add(splitquestions(3))
globalVariables.optionD.Add(splitquestions(4))
globalVariables.Answer.Add(splitquestions(5))
Loop
No, do not use an ArrayList for manipulating a set of objects like this. You should try to think in an Object Oriented way. A QuestionEntry is an entity that contains a QuestionText, 4 possible question answers and one QuestionAnswer.
So let's try this code
Public Class QuestionEntry
Public QuestionText as String
Public ChoiceA as String
Public ChoiceB as String
Public ChoiceC as String
Public ChoiceD as String
Public QuestionAnswer as String
End Class
Dim questions = new List(Of QuestionEntry)()
Dim line As String
Do While sr.Peek() >= 0
line = sr.ReadLine
Console.WriteLine(line)
Dim parts = line.Split(New String() {"##"}, StringSplitOptions.RemoveEmptyEntries)
Dim q = new QuestionEntry()
With q
.QuestionText = parts(0)
.ChoiceA = parts(1)
.ChoiceB = parts(2)
.ChoiceC = parts(3)
.ChoiceD = parts(4)
.QuestionAnswer = parts(5)
End With
questions.Add(q)
Loop
Of course this is just an example and a bit of error checking will be required to make the code more safe. For example before creating the new question entry you should check if the array returned by the split has effectively 6 elements. (parts.Length = 6)
Now all of your text is handled by an instance of a List(Of QuestionEntry) and you could use it like a normal array
Dim qe = questions(0)
Console.WriteLine("Question: " & qe.QuestionText)
Console.WriteLine("Choice A: " & qe.ChoiceA)
Console.WriteLine("Choice B: " & qe.ChoiceB)
Console.WriteLine("Choice C: " & qe.ChoiceC)
Console.WriteLine("Choice D: " & qe.ChoiceD)
Console.ReadLine("Enter your answer:")
The best way to do this is to rely on an existing delimited data parser. The .Split() method is very often horrible for this: performance is sub-par, and there are all kings of edge cases (more than you'd think) where it just doesn't work well. There is even a parser already built into .Net: Microsoft.VisualBasic.FileIO.TextFieldParser.
Additionally, ArrayLists really only exist for pre-.Net 2.0 compatibility. There's is no good reason to ever use one any more. At very least, use a generic List(Of String). In this case, though, your best option is to build a quick class:
Public Class Question
Public Property QuestionText As String
Public Property OptionA As String
Public Property OptionB As String
Public Property OptionC As String
Public Property OptionD As String
Public Property Answer As String
End Class
Now you read your file like this:
Dim results As New List(Of Question)()
Using rdr As New TextFieldParser(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
rdr.Delimiters = new String() {"##"}
Dim row() As String
While Not rdr.EndOfData
row = rdr.ReadFields()
results.Add(New Question() {
QuestionText = row(0),
OptionA = row(1),
OptionB = row(2),
OptionC = row(3),
OptionD = row(4),
Answer = row(5)
})
End While
End Using
Even with the class definition, that's a whole let less code than the original, and it's much easier to maintain, as well.
I'd also be tempted to write this as an Iterator:
Public Iterator Function ReadQuestions(ByVal FileName As String) As IEnumerable(Of Question)
Using rdr As New TextFieldParser(FileName)
rdr.Delimiters = new String() {"##"}
Dim row() As String
While Not rdr.EndOfData
row = rdr.ReadFields()
Yield New Question() {
QuestionText = row(0),
OptionA = row(1),
OptionB = row(2),
OptionC = row(3),
OptionD = row(4),
Answer = row(5)
}
End While
End Using
End Function
I have two final changes to suggest. The first to add a constructor to the Question type that accepts a string array. This would remove one bit of advanced syntax (the object initializer) from the code, and simplify reading through the portion of the code that actually reads the data. The second isto make the ReadQuestions() method a shared member of the Question type. The final result is this:
Public Class Question
Public Property QuestionText As String
Public Property OptionA As String
Public Property OptionB As String
Public Property OptionC As String
Public Property OptionD As String
Public Property Answer As String
Public Sub New(ByVal data() As String)
'Add error handling here
QuestionText = data(0),
OptionA = data(1),
OptionB = data(2),
OptionC = data(3),
OptionD = data(4),
Answer = data(5)
End Sub
Public Shared Iterator Function ReadFromFile(ByVal FileName As String) As IEnumerable(Of Question)
Using rdr As New TextFieldParser(FileName)
rdr.Delimiters = new String() {"##"}
While Not rdr.EndOfData
Yield New Question(rdr.ReadFields())
End While
End Using
End Function
End Class
And you call all this from your existing code like so:
Dim Questions = Question.ReadFromFile(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
For Each q As Question in Questions
'...
Next