How to change this code to find all matches - vb.net

I need to perform a substring match on the ID. For example, search for all the IDs that begin with "AB" and return all matches. ID format is AB1234.
Dim CPosition, HashPostion As Integer
Dim StudentID, Fileline, IdPart As String
Dim SPostion As Char
Dim found As Boolean = False
FileOpen(1, "StudendRecord.txt", OpenMode.Input)
Console.Write("Enter ID to find the email: ")
StudentID = Console.ReadLine()
Do
CPosition = 1
Fileline = LineInput(1)
Do
SPostion = Mid(Fileline, CPosition, 1)
CPosition = CPosition + 1
Loop Until SPostion = "#"
HashPostion = Len(Fileline) - (CPosition - 1)
CPosition = 1
Do
SPostion = Mid(Fileline, CPosition, 1)
IdPart = IdPart + SPostion
If StudentID = IdPart Then
Console.WriteLine("the email: " & Right(Fileline, HashPostion))
found = True
End If
CPosition = CPosition + 1
Loop Until SPostion = "#"
Loop Until EOF(1)
If found = False Then
Console.WriteLine("ID not found.")
End If
FileClose(1)

To use this code add Imports System.IO to the top of the file. This namespace has a File class. File.ReadLines returns and array of the lines in a text file.
I created a simple class to hold the data in the file.
Then a list of the class's type.
I then split each line on the # character getting a 2 element array. The first element will contain the ID and the second element contains the email.
Create a new student and pass the data to the constructor. Then add the new student to the list.
To search the list I used Linq. Linq returns an IEnumerable(Of T) T in this case is StudentRecord.
To search the list I used Linq. I used .ToArray on the resulting IEnumerable because we only need the first element.
Public Class StudentRecord
Public Property ID As String
Public Property Email As String
Public Sub New(id As String, email As String)
Me.ID = id
Me.Email = email
End Sub
End Class
Module Module1
Private StudentRecords As New List(Of StudentRecord)
Public Sub Main()
Dim Records = File.ReadLines("StudentRecord.txt")
For Each line In Records
Dim SplitLine = line.Split("#"c)
Dim stu As New StudentRecord(SplitLine(0), SplitLine(1))
StudentRecords.Add(stu)
Next
Console.Write("Enter ID to find the email: ")
Dim StudentID = Console.ReadLine()
Dim students = From stu In StudentRecords
Where stu.ID.StartsWith(StudentID)
Select stu
If students.Count > 0 Then
For Each stu In students
Console.WriteLine($"Student {stu.ID} email is {stu.Email}")
Next
Else
Console.WriteLine("ID not found.")
End If
Console.ReadLine()
End Sub
End Module

Related

Removing duplicates in Text Box and adding the corresponding values

I have a VB form with three TextBoxes. Here's an example of what I'd like the program to achieve:
So, that's the form ... the program sorts a text file and gets names, goals, and positions. E.g.
Jordan 26 Center
James 10 Mid
Jordan 4 Center
Jack 6 Forward
James 10 Mid
When the update button is clicked, the program should realize that James and Jordan are written twice, remove one of them and add their goals, so it should output:
Jordan 30 Center
James 20 Mid
Jack 6 Forward
To do this I've had the data transferred into ListBoxes which makes it easier to remove duplicates, the data is then transferred back into a multi-line TextBox so it is editable. Here's my code so far. It either gives the wrong results or an index out of range error.
Dim Count1 As Integer
Dim Count2 As Integer
Dim Count3 As Integer
Dim NewInt As Integer
Dim ValOne As Integer
Dim ValTwo As Integer
ListBox1.Items.Clear()
ListBox2.Items.Clear()
ListBox3.Items.Clear()
NewInt = 0
ValOne = 0
ValTwo = 0
ListBox1.Items.AddRange(Players.Text.Split(vbNewLine))
ListBox2.Items.AddRange(Goals.Text.Split(vbNewLine))
ListBox3.Items.AddRange(Positions.Text.Split(vbNewLine))
Count1 = ListBox1.Items.Count
Count2 = ListBox2.Items.Count
Count3 = ListBox3.Items.Count
If Count1 = Count2 And Count1 = Count3 And Count2 = Count3 Then
'Set two counters to compare all words with each other
For iFirstCounter As Integer = 0 To ListBox1.Items.Count - 1
For iSecondCounter As Integer = 0 To ListBox1.Items.Count - 1
'Make sure there will not be an 'out of range' error,
'because you are removing items from the listbox.
iSecondCounter = Convert.ToInt64(iSecondCounter)
iFirstCounter = Convert.ToInt64(iFirstCounter)
ListBox2.Items.RemoveAt(iSecondCounter)
ListBox2.Items.RemoveAt(iFirstCounter)
If iFirstCounter < iSecondCounter Then
ListBox2.Items.Insert(iFirstCounter, NewInt.ToString)
Else
ListBox2.Items.Insert(iSecondCounter, NewInt.ToString)
End If
Next
Next
Players.Text = ""
Goals.Text = ""
Positions.Text = ""
Dim i As Integer
For i = 0 To ListBox1.Items.Count - 1
If Players.Text = "" Then
Players.Text = ListBox1.Items(i)
Else
Players.Text = Players.Text & vbNewLine & ListBox1.Items(i)
End If
Next
Dim a As Integer
For a = 0 To ListBox2.Items.Count - 1
If Goals.Text = "" Then
Goals.Text = ListBox2.Items(a)
Else
Goals.Text = Goals.Text & vbNewLine & ListBox2.Items(a)
End If
Next
Dim b As Integer
For b = 0 To ListBox3.Items.Count - 1
If Positions.Text = "" Then
Positions.Text = ListBox3.Items(b)
Else
Positions.Text = Positions.Text & vbNewLine & ListBox3.Items(b)
End If
Next
Else
MessageBox.Show("The Text Boxes don't contain an equal number of values ... please add more/remove some values")
End If
Could be done in multiple ways, for example:
If TextBox2.Lines.Count > 1 Then
Dim LineList As List(Of String) = TextBox2.Lines.ToList 'textbox lines
Dim NewLines As List(Of String) = TextBox2.Lines.ToList 'can't edit list we're looping over, a copy of lines
Dim NamesList As New List(Of String)
For x = 0 To LineList.Count - 1
Dim linesplit As String() = LineList(x).Split({" "}, StringSplitOptions.RemoveEmptyEntries)
If NamesList.Contains(linesplit(0)) Then
NewLines.Remove(LineList(x))
Else
NamesList.Add(linesplit(0))
End If
Next
TextBox2.Lines = NewLines.ToArray
End If
Here's an example of code that does this via LINQ and Lambdas.
Module Module1
Sub Main()
Dim ungroupedPlayers(1) As String
ungroupedPlayers(0) = "Jordan 26 Center"
ungroupedPlayers(1) = "Jordan 4 Center"
Dim players = ungroupedPlayers.ToList().ConvertAll(Of Player)(Function(x As String) As Player
Dim split() As String = x.Split(" "c)
Dim p As New Player
p.PlayerName = split(0)
p.Count = split(1)
p.Position = split(2)
Return p
End Function)
Dim playersGrouped = From p In players
Group By PlayerName = p.PlayerName Into g = Group
Select PlayerName, Count = g.Sum(Function(ip As Player) ip.Count), Position = g.Min(Function(ip As Player) ip.Position.ToString())
Dim groupedPlayers() As String = playersGrouped.ToList().ConvertAll(Of String)(Function(ip)
Return ip.PlayerName.ToString() & " " & ip.Count.ToString() & " " & ip.Position.ToString()
End Function).ToArray()
For Each groupedPlayer as String in groupedPlayers
Console.WriteLine(groupedPlayer)
Next
Console.Read()
End Sub
Public Class Player
Public PlayerName As String
Public Count As Integer
Public Position As String
End Class
End Module
You don't need heavy ListBox control for working with players data.
Use List(Of T) and create class Player for better readability.
You can remove duplicates before you will display values in your form.
And instead of multiline textbox you can use DataGridView as "right tool for the editing data".
Public Class Player
Public Property Name As String
Public Property Position As String
Public Property Goals As Integer
End
Public Class PlayersForm : Form
Private Sub Form_Load(sender As Object, e As System.EventArgs) Handles MyBase.Load
Dim data As List(Of Player) = LoadPlayersData()
Dim players As List(Of Player) = NormalizeData(data)
' Use DataGridView
Me.DataGridView1.DataSource = players
End Sub
Private Function LoadPlayersData() As List(Of Player)
Dim rawData As String() = File.ReadAllLines("pathToTextFile")
Return rawData.Select(Function(line) LineToPlayer(line)).ToList()
End Function
Private Function NormalizeData(players As List(Of Player)) As List(Of Player)
Return players.Group(Function(player) player.Name)
.Select(Function(group)
Return New Player With
{
.Name = group.Key,
.Position = group.First().Position,
.Goals = group.Sum(Function(player) player.Goals)
}
End Function)
.ToList()
End Function
Private Function LineToPlayer(line As String) As Player
Dim values = line.Split(" "c)
Return New Player With
{
.Name = values(0),
.Position = values(2),
.Goals = Integer.Parse(values(1))
}
End Function
End Class
DataGridView control will automatically update your List(Of Players) when you make any change. which give you possibility to have some other controls which automatically display best scorers for example, without extra converting data from string to integer and back.

VB .NET HTMLAgilityPack Colon Separated Values

Is there a way to get the values within a tag using HTMLAgilityPack?
My variable dataNode is an HtmlAgilityPack.HtmlNode and contains:
Dim doc as New HtmlAgilityPack.HtmlDocument()
doc.LoadHtml("
<div id="container" data="id:12,country:usa,city:oregon,id:13,country:usa,city:atlanta">
Google
</div>
")
Would like to get the value of each id, country,city. They repeat within the tag and have different values.
Dim dataNode as HtmlAgililtyPack.HtmlNode
dataNode = doc.documentNode.SelectSingleNode("//div")
txtbox.text = dataNode.Attributes("id[1]").value
This gives an error System.NullReferenceException
You need the "data" attribute, not the "id" attribute.
Once you have the value of the correct attribute, you will need to parse it into some data structure suitable for holding each part of the data, for example:
Option Infer On
Option Strict On
Module Module1
Public Class LocationDatum
Property ID As Integer
Property Country As String
Property City As String
Public Overrides Function ToString() As String
Return $"ID={ID}, Country={Country}, City={City}"
End Function
End Class
Sub Main()
Dim doc As New HtmlAgilityPack.HtmlDocument()
doc.LoadHtml("
<div id=""container"" data=""id:12,country:usa,city:oregon,id:13,country:usa,city:atlanta"">
Google
</div>
")
Dim dataNode = doc.DocumentNode.SelectSingleNode("//div")
Dim rawData = dataNode.Attributes("data").Value
Dim dataParts = rawData.Split(","c)
Dim locationData As New List(Of LocationDatum)
' A simple way of parsing the data
For i = 0 To dataParts.Count - 1 Step 3
If i + 2 < dataParts.Count Then
Dim id As Integer = -1
Dim country As String = ""
Dim city As String = ""
' used to check all three required parts have been found:
Dim partsFoundFlags = 0
For j = 0 To 2
Dim itemParts = dataParts(i + j).Split(":"c)
Select Case itemParts(0)
Case "id"
id = CInt(itemParts(1))
partsFoundFlags = partsFoundFlags Or 1
Case "country"
country = itemParts(1)
partsFoundFlags = partsFoundFlags Or 2
Case "city"
city = itemParts(1)
partsFoundFlags = partsFoundFlags Or 4
End Select
Next
If partsFoundFlags = 7 Then
locationData.Add(New LocationDatum With {.ID = id, .Country = country, .City = city})
End If
End If
Next
For Each d In locationData
Console.WriteLine(d)
Next
Console.ReadLine()
End Sub
End Module
Which outputs:
ID=12, Country=usa, City=oregon
ID=13, Country=usa, City=atlanta
It is resistant to some data malformations, such as id/city/country being in a different order, and spurious data at the end.
You would, of course, put the parsing code into its own function.

How to store 2 of the same item in a dictionary

I need to store the same item twice in a dictionary because say I have this sentence: 'Hi Example Test Hi' This should output 1, 2, 3, 1 as it needs to store the initial position with the word. Here is the code I have so far:
If System.Text.RegularExpressions.Regex.IsMatch(userInput.Text, "^[a-zA-Z\s]+$") Then
Dim userString As String = userInput.Text 'Refers to the textbox's text.
Dim count As Integer 'Declares the count variable, which will be added to the dictionary as the word's position.
Dim d As New Dictionary(Of String, Integer) 'Declares the dictionary I will iterate through in the future.
Dim d2 As New Dictionary(Of String, Integer)
Dim wordString = userString.ToLower().Split(" "c) 'Puts the input into lower case and splits it apart by detecting each space after each word.
For Each word In wordString
If (d.ContainsKey(word)) Then
Else
d.Add(word, count) 'Adds each word to the dictionary along with the position.
count = count + 1 'Adds to the count variable to iterate through the dictionary.
End If
Next
For Each de In d
For Each dee In d2
output.Text &= de.Value + 1 & ", " & dee.Value + 1 & ", " & Environment.NewLine 'Gathers each word from the dictionary, referencing it by using 'de' and retrieving the key and value.
Next
Next
Else
MessageBox.Show("Your Sentence Is Invalid, It Must Only Contains Letters.") 'Displays a message if the sentence they inputted is invalid.
End If
thanks,
Matt
maybe something like this:
Dim userString As String = "Hi Example Test Hi"
Dim count As Integer = 1 'Declares the count variable, which will be added to the dictionary as the word's position.
Dim d As New List(Of WordCountPos) 'Declares the dictionary I will iterate through in the future.
Dim wordString = userString.ToLower().Split(" "c) 'Puts the input into lower case and splits it apart by detecting each space after each word.
For Each word In wordString
If d.FindIndex(Function(x) String.Equals(x.Word, word)) <> -1 Then
d.Find(Function(x) String.Equals(x.Word, word)).Positions.Add(count)
Else
d.Add(New WordCountPos(word, 1, New List(Of Integer)({count}))) 'Adds each word to the dictionary along with the position
End If
count = count + 1 'Adds to the count variable to iterate through the dictionary.
Next
Dim output As String = ""
For Each de In d
output = String.Concat(output, Environment.NewLine, "Word: ", de.Word, " Count: ", de.Count, ", Positions:", String.Join("|", de.Positions.ToArray))
Next
Console.WriteLine(output)
Class:
Public Class WordCountPos
Public Sub New(w As String, c As String, pos As List(Of Integer))
Me.wcp_w = w
Me.wcp_c = c
Me.wcp_pos = pos
End Sub
Private wcp_w As String
Public Property Word() As String
Get
Return wcp_w
End Get
Set(ByVal value As String)
wcp_w = value
End Set
End Property
Private wcp_c As Integer
Public Property Count() As Integer
Get
Return wcp_c
End Get
Set(ByVal value As Integer)
wcp_c = value
End Set
End Property
Private wcp_pos As List(Of Integer)
Public Property Positions() As List(Of Integer)
Get
Return wcp_pos
End Get
Set(ByVal value As List(Of Integer))
wcp_pos = value
End Set
End Property
End Class

How to indentify the positions that a word occurs in a given text?

I am developing a program where you can input a sentence and then search for a word. The program will then tell you at which positions this word occurs. I have written some code but do not know how to continue.
Module Module1
Sub Main()
Dim Sentence As String
Dim SentenceLength As Integer
Dim L As Integer = 0
Dim LotsofText As String = Console.ReadLine
Console.WriteLine("Enter your word ") : Sentence = Console.ReadLine
For L = 1 To LotsofText.Length
If (Mid(LotsofText, L, 1)) = " " Then
End If
L = L + 1
Dim TextCounter As Integer = 0
Dim MainWord As String = Sentence
Dim CountChar As String = " "
Do While InStr(MainWord, CountChar) > 0
MainWord = Mid(MainWord, 1 + InStr(MainWord, CountChar), Len(MainWord))
TextCounter = TextCounter + 1
'Text = TextCounter + 2
' Console.WriteLine(Text)
Loop
Console.WriteLine(TextCounter)
Console.Write("Press Enter to Exit")
Console.ReadLine()
End Sub
End Module
Transform this piece of code from C# to Visual Basic. match.Index will indicate the position of the given word.
var rx = new Regex("your");
foreach (Match match in rx.Matches("This is your text! This is your text!"))
{
int i = match.Index;
}
To find only words and not sub-strings (for example to ignore "cat" in "catty"):
Dim LotsofText = "catty cat"
Dim Sentence = "cat"
Dim pattern = "\b" & Regex.Escape(Sentence) & "\b"
Dim matches = Regex.Matches(LotsofText, pattern)
For Each m As Match In matches
Debug.Print(m.Index & "") ' 6
Next
If you want to find sub-strings too, you can remove the "\b" parts.
If you add this function to your code:
Public Function GetIndexes(ByVal SearchWithinThis As String, ByVal SearchForThis As String) As List(Of Integer)
Dim Result As New List(Of Integer)
Dim i As Integer = SearchWithinThis.IndexOf(SearchForThis)
While (i <> -1)
Result.Add(i)
i = SearchWithinThis.IndexOf(SearchForThis, i + 1)
End While
Return Result
End Function
And call the function in your code:
Dim Indexes as list(of Integer) = GetIndexes(LotsofText, Sentence)
Now GetIndexes will find all indexes of the word you are searching for within the sentence and put them in the list Indexes.

Storing Objects into a Class & Retrieving upon Request

I seriously need help with my project.
I am trying to store specific jobs into a Class, which then displays in a List Box.
When selecting the List Box, I want the rest of the information to be displayed into a Text Box.
I can add Jobs into the List Box, and the Report button sorts the Job by Earliest to Latest.
I just CANNOT seem to code the Display Button to retrieve the rest of the information.
http://i.stack.imgur.com/0eV5j.png
What am I doing wrong?
My Code:
Public Class Form1
Dim jobList As List(Of UserInformation) = New List(Of UserInformation)
Dim j As UserInformation = New UserInformation()
Private Sub btnReport_Click(sender As Object, e As EventArgs) Handles btnReport.Click
Dim p As UserInformation = New UserInformation()
Dim qty As Integer = jobList.Count - 1
Dim name(qty) As String
Dim deadline(qty) As Date
Dim i As Integer = 0
'fill the array
For i = 0 To qty
p = jobList(i)
name(i) = p.Name
deadline(i) = p.Deadline
Next
'sort the array
Dim done As Boolean = False
While done = False
done = True
For i = 0 To qty - 1
Dim tempName As String
Dim tempDate As Date
If deadline(i) > deadline(i + 1) Then
tempName = name(i)
tempDate = deadline(i)
name(i) = name(i + 1)
deadline(i) = deadline(i + 1)
name(i + 1) = tempName
deadline(i + 1) = tempDate
done = False
End If
Next
End While
lsbReport.Items.Clear()
lblListbox.Text = "List in date order"
For i = 0 To name.Length - 1
Dim str As String
str = name(i) + ", "
str += deadline(i).ToString + "."
lsbReport.Items.Add(str)
Next
End Sub
Private Sub updateListBox()
lsbReport.Items.Clear()
lblListbox.Text = "All people in the List"
For Each person As UserInformation In jobList
Dim str As String
str = person.Name + ", "
str += person.Deadline.ToString + "."
lsbReport.Items.Add(str)
Next
End Sub
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
Dim p As UserInformation = New UserInformation()
p.Name = firstNameText.Text
p.Deadline = lastNameText.Value
jobList.Add(p)
updateListBox()
End Sub
Private Sub btnDisplay_Click(sender As Object, e As EventArgs) Handles btnDisplay.Click
Dim job_info As UserInformation = CType(lsbReport.SelectedItem(), UserInformation)
txtReport.Text = "Job Title: " & job_info.Name() & Environment.NewLine
txtReport.Text &="Job DeadLine: " & job_info.Deadline & Environment.NewLine
txtReport.Text &="Job Description" & job_info.Description
End Sub
End Class
Public Class UserInformation
Public job_deadline As Date
Public job_name As String
Public job_description As String
Public Property Name() As String
Get
Return job_name
End Get
Set(ByVal value As String)
job_name = value
End Set
End Property
Public Property Deadline() As String
Get
Return job_deadline
End Get
Set(ByVal value As String)
job_deadline = value
End Set
End Property
Public Property Description() As String
Get
Return job_description
End Get
Set(ByVal value As String)
job_description = value
End Set
End Property
End Class
The bottom line is that you stored string values to the LBs:
str = person.Name & ", " & person.Deadline.ToString
lsbReport.Items.Add(str)
So, that is what you get back making it difficult to connect the string created with the object it represents.
Listboxes and comboboxes can store objects as well as strings. Simple Demo:
Public Class Employee
Public Property ID As Integer ' a db ID maybe
Public Property Name As String
Public Property Department As String
Public Property HireDate As Date
Public Overrides Function ToString As String
Return Name ' text to show in controls
' in a more realistic class it might be:
' Return String.Format("{0}, {1} ({2})", LastName,
' FirstName, BadgeNumber)
' e.g. "Whitman, Ziggy (123450)"
End Function
End Class
Friend EmpList As New List(of Employee) ' if needed
storing an object to a listbox is simple:
Dim newEmp As Employee
newEmp.Name = "Ziggy"
' set props as needed
myLB.Items.Add(newEmp) ' add to a ListBox directly.
Once you have a class for these things, you have many options. You can store them to a List(Of T) which can be used with a List or ComboBox:
Private EmpList As New List(Of Employee)
...
EmpList.Add(newEmp) ' adding to a list is same as a ListBox
' add from List to a control:
myLB.Items.AddRange(Emps.ToArray)
myLB.SelectedItem will now be an Employee object, which should make displaying details elsewhere simple.
To make it even more efficent, you can use the list as a DataSource so you dont have to add references to the listbox:
myLB.DataSource = EmpList
myLB.DisplayMember = "Name" ' the property to display
myLB.ValueMember = "Id" ' what to use for SelectedValue