Ok. I need to store some records in a file namely data.dat.
Records in the file are sorted by date values. Each block of record starts with its date value along with a $ sign to indicate that a new block of record starts here and ends with a "#" sign to indicate end of the record block.
A sample of a record block would be:
$22/08/2013
(data)
(data)
(data)
#
The file data.dat contains several blocks like this, how can I extract each block in the file storing each in an array using vb.net?
Instead of an array i would use a List(Of T). You could create a custom class:
Class Record
Public Property DateValue As DateTime
Public Property Data As New List(Of String)
End Class
Here's a possible loop to initialize the list from your file:
Dim allData As New List(Of Record)
Dim currentRecord As Record = Nothing
Dim currentData As List(Of String) = Nothing
For Each line In File.ReadLines("data.dat")
If line.StartsWith("$") Then
Dim dt As DateTime
If Date.TryParse(line.Substring(1), dt) Then
currentRecord = New Record()
currentRecord.DateValue = dt
currentData = New List(Of String)
currentRecord.Data = currentData
End If
ElseIf currentRecord IsNot Nothing Then
If line.EndsWith("#") Then
allData.Add(currentRecord)
Else
currentData.Add(line)
End If
End If
Next
It looks to me a Tuple quartet would be ready made for this.
Dim Record As New List(Of Tuple(Of DateTime, String, String, Integer))
Then each field can be accessed by it's item number:
Record(0).Item1
Related
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
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 have an error where I am trying to load a player's name and score to the first open space of the array, but it is creating duplicates of that name over and over in the array. Thanks in advance
Structure Player
Dim Name As String
Dim Score As Integer
End Structure
Public HighScores(100) As Player
For i = 0 To 99
If HighScores(i).Name = "" And HighScores(i).Score = 0 Then
HighScores(i).Name = PlayerName
HighScores(i).Score = CurrentScore
Else
i += 1
End If
Next
Your current code will set the supplied values in every empty index it finds. You need to stop setting the values (exit the loop) once you have found your empty index and set it's value.
For i = 0 To 99
If HighScores(i).Name.Length = 0 AndAlso HighScores(i).Score = 0 Then 'Determines if the index is empty
HighScores(i).Name = PlayerName
HighScores(i).Score = CurrentScore 'Sets the values
Exit For 'Exits the loop
End If
Next
The above code will only run through the loop once if the first index matches your requirements, twice if the first index does not but the second does and so forth.
To avoid looping through the array looking for an empty slot use a List(Of T) as suggested by David Wilson in comments. The T stands for Type and Player is a Type. This restricts your list to only objects of type Player. I included a bit to sort your list using LINQ to Objects. There are in line comments. Although I think a list would be a better approach, The White Wolf's answer with the Exit For should solve your problem.
Structure Player
Public Score As Integer
Public Name As String
'Added a constructor to the structure to make it easy to add new Player
Public Sub New(myScore As Integer, myName As String)
Score = myScore
Name = myName
End Sub
End Structure
Private lstScores As New List(Of Player)
Private Sub BuildList()
lstScores.Add(New Player(500, "Mathew"))
lstScores.Add(New Player(200, "Mark"))
lstScores.Add(New Player(300, "Luke"))
lstScores.Add(New Player(700, "John"))
'Sort and display list
SortList()
End Sub
Private Sub AddScore(strName As String, intScore As Integer)
lstScores.Add(New Player(intScore, strName))
End Sub
Private Sub SortList()
'Note: the original lstScores is not changed
Dim orderedList = From scorer In lstScores Order By scorer.Score Descending Select $"{scorer.Score} - {scorer.Name}"
'orderedList is an IEnumerable(Of String) because the Select part of the LINQ query is a string.
'Using .ToList provides the DataSource of the ListBox with the formatted strings
'Display the sorted list in a list box
ListBox1.DataSource = orderedList.ToList
End Sub
So I am building a stock info retrieval program, and I have a good bit of it done so far. Right now I have it return the price from the Yahoo Finance API by singling out the price in the returned API data and then shove it in a listbox with the stock symbol and price. That part works great, but I want to take it a step further now and be able to do what I want with other parts of that returned data. The normal format for the data is:
"<symbol>", <price>, "<date>", "<time>", etc.
If you take a look at my code, right now I have a Getstockinfo function that retrieves the full API output and converts each object(symbol, price, etc) into a new stringbuilder line, and then separates the lines by the commas using the ModifyLine function.
I want to now get the GetStockInfo to return an array, which would allow me to use that array as I pleased outside of that function(in eventhandlers, etc.)
ANY help would be much appreciated!
Public Function GetStockInfo(ByVal pstrSymbol As String) As String
Dim strURL As String
Dim strApiOutput As String
'Yahoo API
strURL = "http://quote.yahoo.com/d/quotes.csv?" & _
"s=" & pstrSymbol & _
"&d=t" & _
"&f=sl1d1t1c1ohgvj1pp2wern"
strApiOutput = RequestWebData(strURL)
'Create stringbuilder(easier to append, replace and insert data)
Dim strReturn As New System.Text.StringBuilder
'Seperate API output into different lines by using LineFeed
For Each strLine As String In strApiOutput.Split(ControlChars.Lf)
'makes sure line actually exists, if so, seperate and add to array using ModifyLine function
If strLine.Length > 0 Then
strReturn.Append(ModifyLIne(strLine) & Environment.NewLine)
End If
Next
Return strReturn.ToString
End Function
Private Function ModifyLIne(ByVal strLine As String) As String
Dim arrLine() As String
'Splits lines by the commas(the normal api output is as follows: <Obj1>, <Obj2>, <Obj3>, etc
'with Intermediary quotes here and there for certain objects such as Company Name
arrLine = strLine.Split(","c)
decStockPrice = CDec(arrLine(1))
Return decStockPrice.ToString
End Function
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
Dim strSymbol As String = txtSymbol.Text
lstStocks.Items.Add(strSymbol.ToUpper() & " - " & GetStockInfo(strSymbol))
End Sub
I want to take it a step further now and be able to do what I want with other parts of that returned data This will do what you want, just not how you want (arrays are old-school)
Class StockQuote
Public Symbol As String
Public Price As Decimal
Public QuoteDate As DateTime
' etc
End Class
Friend myQuotes As New List(of StockQuote)
Then after you get a quote and determine you want to save it (are you saving all of them?):
Dim SQ as New StockQuote
With SQ
.Symbol = arrLine(0) ' so you can pass an array or list of ticker syms
.Price = Convert.ToDecimal(arrLine(1))
' not much sense saving Time and Date as sep items
' if you are getting multiple quotes for a day (vs ending)
.QuoteDate = combine arrline(2) and arrLine(3) ?
etc
End SQ
myQuotes.Add(SQ)
Now, myQuotes will have a list of all the quotes for all the symbols. Get them like you populated it: txtSym.Text = myQuotes(N).Symbol
I have a custom item class (basically two string values with associated properties), as below:
Public Class itmDataDetails
Private _strDataName As String
Private _strDataValue As String
Public Property strDataName() As String
Get
Return _strDataName
End Get
Set(ByVal value As String)
_strDataName = value
End Set
End Property
Public Property strDataValue() As String
Get
Return _strDataValue
End Get
Set(ByVal value As String)
_strDataValue = value
End Set
End Property
Public Sub New(Optional ByVal strDataNameIn As String = "", Optional ByVal strDataValueIn As String = "")
strDataName = strDataNameIn
strDataValue = strDataValueIn
End Sub
and an ObservableCollection wrapper class around this. I'd like to transpose this ObservableCollection (that is, make the data names into the columns and their associated values into the rows) for display in a WPF ListView.
Here's what I have so far:
Private Sub Transpose()
Dim colGroupedValues = From x In MyBase.Items Group x By Key = x.strDataName Into Group Select strName = Key, colValues = Group
MyBase.Clear()
For Each x In colGroupedValues
MyBase.Add(x)
Next
End Sub
Naturally, this doesn't work as x cannot be added to the ObservableCollection(Of itmDataDetails). Any suggestions on how to accomplish this? I don't know LINQ that well, so I wouldn't be surprised to discover I'm doing it wrong.
Thanks in advance, everyone.
So I think I was asking the wrong question here. What I really wanted was to have each column in a GridView be set to the DataName part of itmDataDetails and its corresponding records be set to the DataValue.
To do this, I followed the helpful guide at: http://weblogs.asp.net/psheriff/archive/2010/03/08/using-a-wpf-listview-as-a-datagrid.aspx
So the whole thing is replaced by:
Private Sub FillDataList()
Dim strConnectionString As String = "INSERT CONNECTION INFO HERE"
Dim strCommandString As String = "INSERT QUERY HERE"
Dim objCommand As New OleDb.OleDbCommand(strCommandString)
Dim objConnection As New OleDb.OleDbConnection(strConnectionString)
Dim objAdapter As New OleDb.OleDbDataAdapter
Dim ds As New DataSet
objConnection.Open()
objAdapter.SelectCommand = objCommand
objCommand.Connection = objConnection
objAdapter.Fill(ds)
lsvData.View = BuildDataView(ds)
lsvData.DataContext = ds.Tables(0)
lsvData.SetBinding(ListView.ItemsSourceProperty, New Binding)
objConnection.Close()
End Sub
Public Function BuildDataView(ByVal ds As DataSet) As GridView
Dim gv As New GridView
For Each item As DataColumn In ds.Tables(0).Columns
Dim gvc As New GridViewColumn
gvc.DisplayMemberBinding = New Binding(item.ColumnName)
gvc.Header = item.ColumnName
gvc.Width = [Double].NaN
gv.Columns.Add(gvc)
Next
Return gv
End Function
This gives me what I wanted. Sorry for the misunderstanding, if there was any. I'll still accept an answer that does this task better than my solution (to be charitable, etc.)
I also suspect that the database engine doesn't matter here, so you could probably do something similar for Oracle databases, etc.