Contents of List(Of String) are not saved - vb.net

I am attempting to parse paragraphs such as the following...
Group 1. Does this or does that. Or Sometimes this. Or that.
Group 2. I do lots of things. But not this. Or that.
Group 3. I do this. I do that. Sometimes this. Sometimes that.
The "Group 1-3" are the org Names, and each following sentence separated by a period is a function.
Code:
Public Sub parseParagraphs(paragraphList As List(Of String))
Dim listOfOrgs As New List(Of EAB_Org)
Dim listOfFuntions As New List(Of String)
Dim orgName As String
For Each item In paragraphList
listOfFuntions.Clear()
Dim words As String() = item.Split(New Char() {"."c}) 'Splits on periods
orgName = words(0) 'Sets the orgName
For index As Integer = 1 To words.Count - 1 'rest of items in list are functions performed
listOfFuntions.Add(words(index))
Next
Dim anOrg As New EAB_Org(orgName, listOfFuntions)
listOfOrgs.Add(anOrg)
Next
End Sub
EAB Class:
Public Class EAB_Org
Dim orgName As String
Dim listOfTasks As List(Of String)
Public Sub New(theOrgName As String, theListOfTasks As List(Of String))
orgName = theOrgName
listOfTasks = theListOfTasks
End Sub
Public Function getOrgName()
Return orgName
End Function
Public Function getListOfTasks()
Return listOfTasks
End Function
End Class
For some reason, when I print out the contents of listOfOrgs, all the org names are correct, but the functions are all of the same and always the last set of functions read in.
Code I use to print:
Public Sub writeExcel(listOfOrgs As List(Of EAB_Org))
For Each anItem In listOfOrgs
Console.WriteLine(anItem.getOrgName)
For Each anotherItem In anItem.getListOfTasks
Console.WriteLine(anotherItem)
Next
Next
End Sub
Output Looks Like:
Group 1
I do this. I do that. Sometimes this. Sometimes that.
Group 2
I do this. I do that. Sometimes this. Sometimes that.
Group 3
I do this. I do that. Sometimes this. Sometimes that.

The problem is that in the constructor for EAB_Org, theListOfTasks is just a pointer to listOfFuntions (which you keep modifying) in the parseParagraphs Sub. In the constructor, you will need to create a new List(Of String) and copy the values from theListOfTasks into it.
Change the constructor to the following:
Public Sub New(theOrgName As String, theListOfTasks As List(Of String))
orgName = theOrgName
listOfTasks = New List(Of String)
For Each item As String In theListOfTasks
listOfTasks.Add(item)
Next
End Sub

Related

Sort specific objects

I have tree classes as follows:
Public Class HtmlSection
Property Name As String
Property SubSections As List(Of HtmlSubSection)
End Class
Public Class HtmlSubSection
Property Name As String
Property SelectedSentences As List(Of HtmlSentence)
End Class
Public Class HtmlSentence
Property Sentence As String
Property Position As Integer
End Class
In below method i am searching for all sentences for each subsection belonging to specific section, at the end i sort those records by Position asc. However sometimes positions have to be changed (directly in sentences) because there could be gaps means after i do OrderBy it will be ordered but it could look like this below. Is there any easy way like linq to change that Positions of that sentences to avoid gaps let's say in the method i shown below.
2
5
77
1001
i would like to change positions starting from 0 in our example:
0
1
2
3
Method:
Public Function GetSelectedSentencesOnSectionLevel(section As HtmlSection) As List(Of HtmlSentence)
Dim sentencesList As New List(Of HtmlSentence)
For Each exSection As HtmlSection In _htmlFactory.SectionsList
If exSection.Name = section.Name Then
Dim sentencesList As New List(Of HtmlSentence)
If Not IsNothing(exSection.SubSections) Then
For Each exSubsection As HtmlSubSection In exSection.SubSections
If Not IsNothing(exSubsection.SelectedSentences) Then
For Each exSentence As HtmlSentence In exSubsection.SelectedSentences
sentencesList.Add(exSentence)
Next
End If
Next
End If
End If
Next
'sort sentences by Posiions ascending
sentencesList = sentencesList.OrderBy(Function(x) x.Position).ToList()
Return sentencesList
End Function
EDIT : more code for helpers:
global class:
Public Class HtmlFactory
Property SectionsList As List(Of HtmlSection)
Sub New()
SectionsList = New List(Of HtmlSection)
End Sub
Sub New(pSectionsList As List(Of HtmlSection))
_SectionsList = pSectionsList
End Sub
Public Sub AddSection(section As HtmlSection)
SectionsList.Add(section)
End Sub
....
Here you are a pure LINQ solution.
Dim index As Integer = -1
Dim sectionName As String
Dim allTheSections As List(Of HtmlSection)
Dim sentenceList = allTheSections _
.Where(Function(sect) _
sect.SubSections IsNot Nothing _
AndAlso sect.Name.Equals(sectionName, StringComparison.OrdinalIgnoreCase)) _
.SelectMany(Function(sect) sect.SubSections) _
.Where(Function(subSect) subSect.SelectedSentences IsNot Nothing) _
.SelectMany(Function(subSect) subSect.SelectedSentences) _
.OrderBy(Function(ss) ss.Position) _
.Select(Function(ss)
index += 1
Return New HtmlSentence With {.Position = index, .Sentence = ss.Sentence}
End Function) _
.ToList()
In this example, allTheSections is where you exSection does coming from.

Split() doesn't work properly

well I'm doing a computing assessment and well I've ran into an issue with splitting a string. For some reason when the string splits the array stores the whole thing in Variable(0). The error that occurs is when it tries to assign TicketID(Index) a value, it says that the array is out of bound.
Here's the code:
Private Sub ReadInformation(ByRef TicketID() As String, CustomerID() As String, PurchaseMethod() As Char, NumberOfTickets() As Integer, FileName As String)
Dim Line, TextArray(3) As String
Dim Index As Integer
FileOpen(1, FileName, OpenMode.Input)
For Index = 0 To 499
Input(1, Line)
TextArray = Line.Split(",")
CustomerID(Index) = TextArray(0)
TicketID(Index) = TextArray(1)
NumberOfTickets(Index) = TextArray(2)
PurchaseMethod(Index) = TextArray(3)
MessageBox.Show(CustomerID(Index))
Next
FileClose()
End Sub
Here's the first 10 lines of the TextFile I'm trying to read:
C001,F3,10,S
C002,F3,2,O
C003,F3,3,S
C004,W2,9,S
C005,T3,10,S
C006,F3,2,S
C007,W1,3,O
C008,W3,1,O
C009,T2,2,S
C010,F2,9,O
Here's the Error Message I receive:
Error Message
I would use some Lists instead of arrays. In this way you don't have to worry about length of the arrays or if there are fewer lines than 500. Of course, using the more advanced NET Framework methods of the File.IO namespace is a must
Private Sub ReadInformation(TicketID As List(Of String), _
CustomerID As List(Of String), _
PurchaseMethod As List(Of Char), _
NumberOfTickets As List(Of Integer), _
FileName As String)
for each line in File.ReadLines(FileName)
Dim TextArray = Line.Split(","c)
if TextArray.Length > 3 Then
CustomerID.Add(TextArray(0))
TicketID.Add(TextArray(1))
' This line works just because you have Option Strict Off
' It should be changed as soon as possible
NumberOfTickets.Add(TextArray(2))
PurchaseMethod.Add(TextArray(3))
End If
Next
End Sub
You can call this version of your code declaring the 4 lists
Dim TicketID = New List(Of String)()
Dim CustomerID = New List(Of String)()
Dim PurchaseMethod = New List(Of Char)()
Dim NumberOfTickets = New List(Of Integer)()
ReadInformation(TicketID, CustomerID, PurchaseMethod, NumberOfTickets, FileName)
Another approach more Object Oriented is to create a class that represent a line of your data. Inside the loop you create instances of that class and add the instance to a single List
Public Class CustomerData
Public Property TicketID As String
Public Property CustomerID As String
Public Property NumberOfTickets As Integer
Public Property PurchaseMethod As Char
End Class
Now the loop becomes
Private Function ReadInformation(FileName As String) as List(Of CustomerData)
Dim custData = New List(Of CustomerData)()
For Each line in File.ReadLines(FileName)
Dim TextArray = Line.Split(","c)
if TextArray.Length > 3 Then
Dim data = new CustomerData()
data.CustomerID = TextArray(0)
data.TicketID = TextArray(1)
data.NumberOfTickets = TextArray(2)
data.PurchaseMethod = TextArray(3)
custData.Add(data)
End If
Next
return custData
End Function
This version requires the declaration of just one list
You can call this version of your code passing just the filename and receiving the result fo the function
Dim customers = ReadInformation(FileName)
For Each cust in customers
Console.WriteLine(cust.CustomerID)
...
Next
Or use it as an array
Dim theFirstCustomer = customers[0]
Console.WriteLine(theFirstCustomer.CustomerID)

Writing Entries in a VB Dictionary into a Text File

I'm working on VB in college and am running into a snag with one of my assignments. Can someone help? I'm aiming to try to take the following dictionary code:
Public Class Inventory
Public ItemInventory As New Dictionary(Of String, Item)
Public Function iItem(ByVal key As String) As Item
Return ItemInventory(key)
End Function
Public Sub addItem(ByVal item As String, ByVal Desc As String, ByVal DRate As Double, ByVal WRate As Double, _
ByVal MRate As Double, ByVal Quantity As Integer)
With ItemInventory
.Add(item, New Item(item, Desc, DRate, WRate, MRate, Quantity))
End With
End Sub
Public Sub removeItem(ByVal item As String)
With ItemInventory
.Remove(item)
End With
End Sub
Public Function returnKeys() As String()
Dim Keys() As String
With ItemInventory
Keys = .Keys.ToList.ToArray
End With
Return Keys
End Function
End Class
Not pretty, I know, but it gets the job done, that's all I aim to do. Now a bit of this also has to do with displaying a dictionary item in the program, which I'm also having issues with, however, I'd like to take this one step at a time, so we'll get to that later, if possible.
As per writing, this is my current code for reading and writing:
Imports System.IO
Public Class InventoryFile
Public Sub RFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim infile As StreamReader = File.OpenText(FPath)
Dim entireLine As String = infile.ReadLine()
Dim fields() As String = entireLine.Split(","c)
While infile.EndOfStream
Dim dItem As New Item
dItem.ID = fields(0)
dItem.Description = fields(1)
dItem.Daily = fields(2)
dItem.Weekly = fields(3)
dItem.Monthly = fields(4)
dItem.Quantity = fields(5)
'AddItem
dInventory.addItem(dItem.ID, dItem.Description, dItem.Daily, dItem.Weekly, _
dItem.Monthly, dItem.Quantity)
End While
End Sub
Public Sub WFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim outfile As StreamWriter = File.CreateText(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
Next
End Sub
End Class
I hope that posted right. Now, reading in, as far as I understand, works just fine, in terms of a file going into a dictionary, however 'WFile', my StreamWriter, is what's got me stumped. Can someone help me with that? Likewise, its supposed to close and write to the file upon closing, and my only code for the close button is the Me.Close() command. How would I write a trigger to make the program write to the file? Know that the main form code, and my 'InventoryFile' are both separate classes, so this has to be done by referencing the other classes in question
Try this to write each dictionary key/value pair on a single line in the file:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}", Item.Key, Item.Value)
Next
UPDATE:
Since Item is both the variable used in the loop and the name of a class, change it to another name like singleItem and then pull out the other pieces of information from the Value portion of the key/value pair, because the Value is actually an Item class object, like this:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each singleItem As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}:{2}:{3}:{4}:{5}:{6}", singleItem.Key, singleItem.Value.ID, singleItem.Value.Description, singleItem.Value.Daily, singleItem.Value.Weekly, singleItem.Value.Monthly, singleItem.Value.Quantity)
Next
In order to create the format your RFile procedure can read you need something like this:
For Each kvp As KeyValuePair(Of String, Item) In dInventory.ItemInventory
' using Karl's compact approach, but add commas
' since your Read expects them
outfile.Write("{0},{1},{2},{3},{4}...", kvp.Key, kvp.Value.ID, _
kvp.Value.Description, ... kvp.Value.Quantity.Tostring)
' the Value part of the fvp is another class, right? `Value.XXX` should drill
' into it to get to the members
Next
outfile.flush
outfile.close ' look into 'Using...'
Thats NOT how I would do it, look into serialization for a less fragile way to read/write the data. It is basically meant for just this sort of thing: save class data for later, and it is not that hard to use.
as for hooking it up, the button click would just call Inventory.WFile

Recursive For Each loop doesnt seem to recurse properly

For some reason, it seems that the outer block doesn't seem to update recursively, as I expected it to. I want the loops to add all directories within "C:\Users\Drise"to the array internaldirs(). Any advice on the correct way to do this, as it seems I'm doing it improperly?
Static internaldirs() As String
internaldirs.add("C:\Users\Drise")
For Each internaldir As String In internaldirs
For Each direc As String In Directory.GetDirectories(internaldir)
internaldirs.Add(direc)
Next
Next
Solution:
Sub recursivedirs()
Static internaldirs As New List(Of String)
Try
If internaldirs(0) = "C:\Users\Drise" Then
Call AddDirToList(internaldirs, internaldirs(0))
End If
Catch
internaldirs.Add("C:\Users\Drise")
Call AddDirToList(internaldirs, internaldirs(0))
End Try
End Sub
Private Sub AddDirToList(ByRef dirs As List(Of String), ByVal currentDir As String)
dirs.Add(currentDir)
Try
For Each subDir As String In Directory.GetDirectories(currentDir)
AddDirToList(dirs, subDir)
Next
Catch
End Try
Short answer is: you can't modify a collection (internaldirs) that you're iterating over.
Longer answer: Looks like you're trying to build a string array listing the folder in the directory tree. A better way would be to use a List and a recursive function.
Static dirs As List(Of String)
dirs = New List(Of String)
AddDirToList(dirs, "C:\Users\Drise")
Private Sub AddDirToList (dirs as List(Of String), currentDir as String)
dirs.Add(currentDir)
For Each subDir As String In Directory.GetDirectories(currentDir)
AddDirToList(dirs, currentDir)
Next
End Sub
Please excuse any syntax issues. I'm more of a C# guy.
As Andrew Cooper said, you can't modify a collection being used in a For Each loop.
You can do it with an index counter.
Dim internaldir As List(Of String)
internaldir.Add("C:\Users\Drise")
Dim i As Integer = 0
Do Until i >= internaldir.Count
Dim internaldir As String = internaldirs(i)
For Each currentdir As String In Directory.GetDirectories(internaldir)
internaldirs.Add(currentdir)
Next
i += 1
Loop
' If you want an array as output, use:
Dim array As String() = internaldirs.ToArray()

How to convert a string of key/value pairs to HashTable or Dictionary or?

In VB.NET, how can I convert the following string into some kind of key/value type such as a Hashtable, Dictionary, etc?
"Name=Fred;Birthday=19-June-1906;ID=12345"
I want to extract Birthday or ID without having to split the string into an array.
EDIT: I'd prefer not to split the string into an array in case the format of the string changes later. I don't have control over the string. What if someone switches the order around or adds another element?
I’m currently unable to test this, lacking a VB compiler, but the following solution should also work, and it has the advantage of not requiring an explicit loop. It uses the Linq method ToDictionary and two nested Split operations:
Dim s = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim d = s.Split(";"c).Select(Function (kvp) kvp.Split("="c)) _
.ToDictionary( _
Function (kvp) kvp(0), _
Function (kvp) kvp(1))
First, we split on the outer delimiter (i.e. the semi-colon). From the resulting array, we select by splitting again, this time on =. The resulting array of arrays is converted to a dictionary by specifying that the first item is to become the key and the second is to become the value (the identifier kvp stands for “key-value pair”).
Since I can’t check the exact VB syntax and the above may contain subtle errors, here is the equivalent C# code (tested for correctness):
var s = "Name=Fred;Birthday=19-June-1906;ID=12345";
var d = s.Split(';').Select(kvp => kvp.Split('='))
.ToDictionary(kvp => kvp[0], kvp => kvp[1]);
Not sure why you don't want to split it. If you're sure there won't be any extra = or ; then you could just do:
Dim s As String = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim d As New Dictionary(Of String, String)
For Each temp As String In s.Split(";"c)
Dim index As Int32 = temp.IndexOf("="c)
d.Add(temp.Substring(0, index), temp.Substring(index + 1))
Next
Which might not be beautiful, but is very easy to understand.
input.Split(";"c) returns an array of key/value:
{ "Name=Fred", "Birthday=19-June-1906" , "ID=12345" }
so pair.Split("="c) returns { "Name", "Fred" } etc
If you want an alternative to doing a String.Split; there is always Regular Expressions as an alternative:
Dim map As Dictionary(Of String, String) = New Dictionary(Of String, String)
Dim match As Match = Regex.Match("Name=Fred;Birthday=19-June-1906;ID=12345", "(?<Name>[^=]*)=(?<Value>[^;]*);?")
While (match.Success)
map.Add(match.Groups("Name").Value, match.Groups("Value").Value)
match = match.NextMatch()
End While
The regular expression itself could be beefed up to better handle whitespace between key/value's and pair's but you hopefully get the idea. This should only pass through the string once to build up a string dictionary of keys and values.
Dim persSeparator as string=";"
Dim keyValSeparator as string="=";
Dim allPersons As New Dictionary(Of String, Person)
Dim str As String = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim parts As New List(Of String)(str.Split(persSeparator.ToCharArray)) 'why dont want you to split this string??
Dim person As New Person
For Each part As String In parts
Dim keyValue() As String = part.Split(keyValSeparator.toCharArray())
Select Case keyValue(0).ToUpper
Case "ID"
person.ID = keyValue(1)
Case "NAME"
person.Name = keyValue(1)
Case "BIRTHDAY"
person.BirthDay= keyValue(1)
End Select
Next
If Not allPersons.ContainsKey(person.ID) Then
allPersons.Add(person.ID, person)
End If
Public Class Person
Private _name As String
Private _birthday As String
Private _id As String = String.Empty
Public Sub New()
End Sub
Public Sub New(ByVal id As String)
Me._id = id
End Sub
Public Sub New(ByVal id As String, ByVal name As String)
Me._id = id
Me._name = name
End Sub
Public Sub New(ByVal id As String, ByVal name As String, ByVal birthday As String)
Me._id = id
Me._name = name
Me._birthday = birthday
End Sub
Public Property ID() As String
Get
Return Me._id
End Get
Set(ByVal value As String)
Me._id = value
End Set
End Property
Public Property Name() As String
Get
Return Me._name
End Get
Set(ByVal value As String)
Me._name = value
End Set
End Property
Public Property BirthDay() As String
Get
Return Me._birthday
End Get
Set(ByVal value As String)
Me._birthday = value
End Set
End Property
Public Overrides Function Equals(ByVal obj As Object) As Boolean
If TypeOf obj Is Person AndAlso Not obj Is Nothing Then
Return String.Compare(Me._id, DirectCast(obj, Person).ID) = 0
Else : Return False
End If
End Function
End Class
If you were just wanting to extract the birthday and ID from the string and place as a value pair in some sort of dictionary, for simplicity I would use regular expressions and then a generic dictionary (of string, valuepair structure). Something like this:
Imports System.Text.RegularExpressions
Imports System.Collections.Generic
Sub Main()
Dim Person As New Dictionary(Of String, ValuePair)
Dim s As String = "Name=Fred;Birthday=19-June-1906;ID=12"
Dim r As Regex = New Regex("Name=(.*);Birthday=(.*);ID=(.*$)")
Dim m As Match = r.Match(s)
Person.Add(CStr(m.Groups(1).Value), _
New ValuePair(CDate(m.Groups(2).Value), CInt(m.Groups(3).Value)))
Console.WriteLine(Person("Fred").Birthday.ToString)
Console.WriteLine(Person("Fred").ID.ToString)
Console.Read()
End Sub
Friend Structure ValuePair
Private _birthday As Date
Private _ID As Int32
Public ReadOnly Property ID() As Int32
Get
Return _ID
End Get
End Property
Public ReadOnly Property Birthday() As Date
Get
Return _birthday
End Get
End Property
Sub New(ByVal Birthday As Date, ByVal ID As Int32)
_birthday = Birthday
_ID = ID
End Sub
End Structure