Remove duplicates from List(Of T) - vb.net

How can I remove my duplicates in the List(Of String)? I was under the assumption that it could work with List(Of T).Distinct, but my result says otherwise. What am I doing wrong? Or what do I have to change to remove the duplicate items in the List(Of T)?
I have read something on the worldwide web about hash something, but I don't think that is really necessary.
This is my code where the list is generated (works with Autodesk Inventor).
Private Function CountCylinders(ByVal oDef As AssemblyComponentDefinition) As Integer
' Lets list all cylinder segments found in the assembly
' we will need the document name to do this.
' the initial value is nothing, if, after counting
' this is still the case, there are no cylinders.
Dim oList As New List(Of String)
' Loop through all of the occurences found in the assembly
For Each oOccurrence As ComponentOccurrence In oDef.Occurrences
' Get the occurence document
Dim oOccurenceDocument As Document
oOccurenceDocument = oOccurrence.Definition.Document
' Check if the occurence document name contains cylinder
If oOccurenceDocument.FullFileName.Contains("Cylinder") Then
' Get the cylinder filename
Dim oCylinder As String
oCylinder = oOccurenceDocument.FullFileName
' Get the filename w/o extension
oCylinder = IO.Path.GetFileNameWithoutExtension(oCylinder)
' Remove the segment mark.
oCylinder = oCylinder.Remove(oCylinder.LastIndexOf("_"), oCylinder.Length - oCylinder.LastIndexOf("_"))
oList.Add(oCylinder)
Debug.Print("add : " & oCylinder)
End If
Next
' Delete the duplicates in the list
oList.Distinct()
' TODO: can be removed.
Debug.Print("Total number of cylinders = " & oList.Count)
' Return the number of cylinders
CountCylinders = oList.Count
End Function
Here is my debug output from the code:
add : Cylinder_1
add : Cylinder_2
add : Cylinder_2
add : Cylinder_2
add : Cylinder_2
add : Cylinder_2
add : Cylinder_7
Total number of cylinders = 7

This is the answer you're looking for thanks to dotnetperls.com VB.NET Remove Duplicates From List
ListOfString.Distinct().ToList

Imports System.Linq
...
Dim oList As New List(Of String)
oList.Add("My Cylinder")
oList = oList.Distinct.ToList()
It's necessary to include System.Linq.

Function RemoveDuplicate(ByVal TheList As List(Of String)) As List(Of String)
Dim Result As New List(Of String)
Dim Exist As Boolean = False
For Each ElementString As String In TheList
Exist = False
For Each ElementStringInResult As String In Result
If ElementString = ElementStringInResult Then
Exist = True
Exit For
End If
Next
If Not Exist Then
Result.Add(ElementString)
End If
Next
Return Result
End Function

Related

Get a specific value from the line in brackets (Visual Studio 2019)

I would like to ask for your help regarding my problem. I want to create a module for my program where it would read .txt file, find a specific value and insert it to the text box.
As an example I have a text file called system.txt which contains single line text. The text is something like this:
[Name=John][Last Name=xxx_xxx][Address=xxxx][Age=22][Phone Number=8454845]
What i want to do is to get only the last name value "xxx_xxx" which every time can be different and insert it to my form's text box
Im totally new in programming, was looking for the other examples but couldnt find anything what would fit exactly to my situation.
Here is what i could write so far but i dont have any idea if there is any logic in my code:
Dim field As New List(Of String)
Private Sub readcrnFile()
For Each line In File.ReadAllLines(C:\test\test_1\db\update\network\system.txt)
For i = 1 To 3
If line.Contains("Last Name=" & i) Then
field.Add(line.Substring(line.IndexOf("=") + 2))
End If
Next
Next
End Sub
Im
You can get this down to a function with a single line of code:
Private Function readcrnFile(fileName As String) As IEnumerable(Of String)
Return File.ReadLines(fileName).Where(Function(line) RegEx.IsMatch(line, "[[[]Last Name=(?<LastName>[^]]+)]").Select(Function(line) RegEx.Match(line, exp).Groups("LastName").Value)
End Function
But for readability/maintainability and to avoid repeating the expression evaluation on each line I'd spread it out a bit:
Private Function readcrnFile(fileName As String) As IEnumerable(Of String)
Dim exp As New RegEx("[[[]Last Name=(?<LastName>[^]]+)]")
Return File.ReadLines(fileName).
Select(Function(line) exp.Match(line)).
Where(Function(m) m.Success).
Select(Function(m) m.Groups("LastName").Value)
End Function
See a simple example of the expression here:
https://dotnetfiddle.net/gJf3su
Dim strval As String = " [Name=John][Last Name=xxx_xxx][Address=xxxx][Age=22][Phone Number=8454845]"
Dim strline() As String = strval.Split(New String() {"[", "]"}, StringSplitOptions.RemoveEmptyEntries) _
.Where(Function(s) Not String.IsNullOrWhiteSpace(s)) _
.ToArray()
Dim lastnameArray() = strline(1).Split("=")
Dim lastname = lastnameArray(1).ToString()
Using your sample data...
I read the file and trim off the first and last bracket symbol. The small c following the the 2 strings tell the compiler that this is a Char. The braces enclosed an array of Char which is what the Trim method expects.
Next we split the file text into an array of strings with the .Split method. We need to use the overload that accepts a String. Although the docs show Split(String, StringSplitOptions), I could only get it to work with a string array with a single element. Split(String(), StringSplitOptions)
Then I looped through the string array called splits, checking for and element that starts with "Last Name=". As soon as we find it we return a substring that starts at position 10 (starts at zero).
If no match is found, an empty string is returned.
Private Function readcrnFile() As String
Dim LineInput = File.ReadAllText("system.txt").Trim({"["c, "]"c})
Dim splits = LineInput.Split({"]["}, StringSplitOptions.None)
For Each s In splits
If s.StartsWith("Last Name=") Then
Return s.Substring(10)
End If
Next
Return ""
End Function
Usage...
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TextBox1.Text = readcrnFile()
End Sub
You can easily split that line in an array of strings using as separators the [ and ] brackets and removing any empty string from the result.
Dim input As String = "[Name=John][Last Name=xxx_xxx][Address=xxxx][Age=22][Phone Number=8454845]"
Dim parts = input.Split(New Char() {"["c, "]"c}, StringSplitOptions.RemoveEmptyEntries)
At this point you have an array of strings and you can loop over it to find the entry that starts with the last name key, when you find it you can split at the = character and get the second element of the array
For Each p As String In parts
If p.StartsWith("Last Name") Then
Dim data = p.Split("="c)
field.Add(data(1))
Exit For
End If
Next
Of course, if you are sure that the second entry in each line is the Last Name entry then you can remove the loop and go directly for the entry
Dim data = parts(1).Split("="c)
A more sophisticated way to remove the for each loop with a single line is using some of the IEnumerable extensions available in the Linq namespace.
So, for example, the loop above could be replaced with
field.Add((parts.FirstOrDefault(Function(x) x.StartsWith("Last Name"))).Split("="c)(1))
As you can see, it is a lot more obscure and probably not a good way to do it anyway because there is no check on the eventuality that if the Last Name key is missing in the input string
You should first know the difference between ReadAllLines() and ReadLines().
Then, here's an example using only two simple string manipulation functions, String.IndexOf() and String.Substring():
Sub Main(args As String())
Dim entryMarker As String = "[Last Name="
Dim closingMarker As String = "]"
Dim FileName As String = "C:\test\test_1\db\update\network\system.txt"
Dim value As String = readcrnFile(entryMarker, closingMarker, FileName)
If Not IsNothing(value) Then
Console.WriteLine("value = " & value)
Else
Console.WriteLine("Entry not found")
End If
Console.Write("Press Enter to Quit...")
Console.ReadKey()
End Sub
Private Function readcrnFile(ByVal entry As String, ByVal closingMarker As String, ByVal fileName As String) As String
Dim entryIndex As Integer
Dim closingIndex As Integer
For Each line In File.ReadLines(fileName)
entryIndex = line.IndexOf(entry) ' see if the marker is in our line
If entryIndex <> -1 Then
closingIndex = line.IndexOf(closingMarker, entryIndex + entry.Length) ' find first "]" AFTER our entry marker
If closingIndex <> -1 Then
' calculate the starting position and length of the value after the entry marker
Dim startAt As Integer = entryIndex + entry.Length
Dim length As Integer = closingIndex - startAt
Return line.Substring(startAt, length)
End If
End If
Next
Return Nothing
End Function

Two List(Of String), when I add a value to one it gets added to the other

I'm getting the folders in two different directories and putting their names into two listviews, but if one folder name exist in only one directory I would like to enter nothing into the other list and vice versa, unless the folder name is in both, then I just want the name in both lists.
Here is the code I am using:
Private folders As New List(Of String), oFolders As New List(Of String), AllFoldrs As New List(Of DirectoryInfo), CurFdr As DirectoryInfo
Private Sub Get_LVItems()
folders.Clear() : oFolders.Clear() : CurLV.Items.Clear() : AllFoldrs.Clear() 'Empty folders List and CurrentLV items
Dim LV As ListView = CurLV, oLV As ListView = OtherLv
Dim Pth As String = CurTV.SelectedNode.Name, oPth As String = ""
Dim Dinfo As New DirectoryInfo(Pth), oDinfo As DirectoryInfo = Nothing
Dim TmpFoldrs As New List(Of DirectoryInfo), oTmpFoldrs As New List(Of DirectoryInfo)
If Not IsNothing(OtherTV.SelectedNode) Then
End If
If TVs_Syncd Then
oPth = OtherTV.SelectedNode.Name : oDinfo = New DirectoryInfo(oPth)
TmpFoldrs.AddRange(Dinfo.GetDirectories) : oTmpFoldrs.AddRange(oDinfo.GetDirectories)
AllFoldrs.AddRange(Dinfo.GetDirectories) : AllFoldrs.AddRange(oDinfo.GetDirectories)
AllFoldrs.Sort(AddressOf SrtAllFdrs)
Do While AllFoldrs.Count > 0
CurFdr = AllFoldrs(0)
Dim Found_fdr As DirectoryInfo = Nothing, oFound_fdr As DirectoryInfo = Nothing
For Each fdr As DirectoryInfo In TmpFoldrs
If fdr.Name = CurFdr.Name Then Found_fdr = fdr : Exit For
Next
If IsNothing(Found_fdr) Then folders.Add(Nothing) Else folders.Add(Found_fdr.FullName)
For Each ofdr As DirectoryInfo In oTmpFoldrs
If ofdr.Name = CurFdr.Name Then oFound_fdr = ofdr : Exit For
Next
If IsNothing(oFound_fdr) Then oFolders.Add(Nothing) Else oFolders.Add(oFound_fdr.FullName)
AllFoldrs.RemoveAll(AddressOf RemDirs)'After adding a folder to both collections (folders & oFolders) all instances of that folder get removed from AllFolders
Loop
LoadListView(oLV)
folders.Clear()
folders = oFolders
LoadListView(LV)
Else
folders.AddRange(Directory.GetDirectories(Pth))
LoadListView(LV)
End If
End Sub
two functions:
for removing all instances of the folder name just processed and
the sort function for sorting AllFoldrs after adding all the folder names to it:
Public Function RemDirs(dir As DirectoryInfo) As Boolean
Return dir.Name = CurFdr.Name
End Function
Public Function SrtAllFdrs(ByVal X As DirectoryInfo, ByVal Y As DirectoryInfo) As Integer
Return X.Name.CompareTo(Y.Name)
End Function
I add both directories folders to AllFolders and each directories folders to their own Tmp List.
In the first For Each loop, I see if the current folder name exists in tmp list, if it does I put the value into Found_fdr then when I add it to the list(Of String) I am using to fill list A, it gets added to the other list(Of String) at the same time. I's baffling me!
This is much more to the point than my last question that got no answers, which really didn't surprise me. Anyone? Please...

Delete duplicates from list

I have the following class :
Public Class titlesclass
Public Property Link As String
Public Property Title As String
Public Function Clear()
Link.Distinct().ToArray()
Title.Distinct().ToArray()
End Function
End Class
And the following code :
For Each title As Match In (New Regex(pattern).Matches(content)) 'Since you are only pulling a few strings, I thought a regex would be better.
Dim letitre As New titlesclass
letitre.Link = title.Groups("Data").Value
letitre.Title = title.Groups("Dataa").Value
lestitres.Add(letitre)
'tempTitles2.Add(title.Groups("Dataa").Value)
Next
I tried to delete the duplicated strings using the simple way
Dim titles2 = lestitres.Distinct().ToArray()
And calling the class function :
lestitres.Clear()
But the both propositions didn't work , i know that i'm missing something very simple but still can't find what it is
Easier to use a class that already implements IComparable:
Dim query = From title In Regex.Matches(content, pattern).Cast(Of Match)
Select Tuple.Create(title.Groups("Data").Value, title.Groups("Dataa").Value)
For Each letitre In query.Distinct
Debug.Print(letitre.Item1 & ", " & letitre.Item2)
Next
or Anonymous Types:
Dim query = From title In Regex.Matches(content, pattern).Cast(Of Match)
Select New With {Key .Link = title.Groups("Data").Value,
Key .Title = title.Groups("Dataa").Value}
For Each letitre In query.Distinct
Debug.Print(letitre.Link & ", " & letitre.Title)
Next
Ok, Since I notice you are using a ClassHere is one option you can do in order to not add duplicate items to your List within a class.I'm using a console Application to write this example, it shouldn't be too hard to understand and convert to a Windows Form Application if need be.
Module Module1
Sub Main()
Dim titlesClass = New Titles_Class()
titlesClass.addNewTitle("myTitle") ''adds successfully
titlesClass.addNewTitle("myTitle") '' doesn't add
End Sub
Public Class Titles_Class
Private Property Title() As String
Private Property TitleArray() As List(Of String)
Public Sub New()
TitleArray = New List(Of String)()
End Sub
Public Sub addNewTitle(title As String)
Dim added = False
If Not taken(title) Then
Me.TitleArray.Add(title)
added = True
End If
Console.WriteLine(String.Format("{0}", If(added, $"{title} has been added", $"{title} already exists")))
End Sub
Private Function taken(item As String) As Boolean
Dim foundItem As Boolean = False
If Not String.IsNullOrEmpty(item) Then
foundItem = Me.TitleArray.Any(Function(c) -1 < c.IndexOf(item))
End If
Return foundItem
End Function
End Class
End Module
Another option would be to use a HashSet, It will never add a duplicate item, so even if you add an item with the same value, it wont add it and wont throw an error
Sub Main()
Dim titlesClass = New HashSet(Of String)
titlesClass.Add("myTitle") ''adds successfully
titlesClass.Add("myTitle") '' doesn't add
For Each title As String In titlesClass
Console.WriteLine(title)
Next
End Sub
With all of that aside, have you thought about using a Dictionary so that you could have the title as the key and the link as the value, that would be another way you could not have a list (dictionary) contain duplicate items

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)

Get ValueMember of Selected item in ListBox

I've seen a couple of posts asking a similar question but I have not been able to duplicate the answers in my code successfully.
The following code adds items and their value member to a list box.
Public Shared Sub ListFiles(hTab As Hashtable)
Debug.Print("create file and key" & Now)
Dim Enumerator As IDictionaryEnumerator
Enumerator = hTab.GetEnumerator()
Dim MyKeys As ICollection
Dim Key As Object
MyKeys = hTab.Keys()
If (hTab.Count > 0) Then
For Each Key In MyKeys
Dim sfileName As String = hTab(Key)
Dim first As Integer = sfileName.IndexOf("_")
Dim last As Integer = sfileName.LastIndexOfAny("_")
Dim first2 = (first + 1)
Dim splitFile = sfileName.Substring(first2)
frmViewFiles.ListBox1.Items.Add(splitFile)
frmViewFiles.ListBox1.ValueMember = Key
frmViewFiles.ListBox1.SelectedValue = Key
Next
End If
End Sub
When I run my code to get the selected items value member
Dim file = ListBox1.ValueMember.ToString()
I can acess the first item I choose but subsequent selections dont change the value member to that of the selected item.
Please direct me.
Thank you for your answers. this is my new code:
Public Shared Sub runListFiles(CustomerId As String)
Dim cfp As New CloudFilesProvider(cloudId)
Dim containerObjectList As IEnumerable(Of ContainerObject) = cfp.ListObjects(container:="EstherTest", identity:=cloudId, prefix:=CustomerId & "_")
For Each file As ContainerObject In containerObjectList
Dim sFullFileName As String = file.Name
Dim first As Integer = sFullFileName.IndexOf("_")
Dim first2 = (first + 1)
Dim splitFile = sFullFileName.Substring(first2)
'frmViewFiles.ListBox1.Items.Add(splitFile)
'frmViewFiles.ListBox1.ValueMember = sFullFileName
Dim fb = New myFile
fb.FileName = splitFile
fb.FullPath = sFullFileName
frmViewFiles.ListBox1.Items.Add(fb)
frmViewFiles.ListBox1.DisplayMember = fb.FileName
frmViewFiles.ListBox1.ValueMember = fb.FullPath
This is my class:
Public Class myFile
Public Property FileName As String
Public Property FullPath As String
Public Sub New(f As String, b As String)
FileName = f
FullPath = b
End Sub
End Class
Please see my comment below and assist
ValueMember is supposed to indicate the property name of an object added to the Items collection: the property to use as the actual value for the items in the ListControl.
You are not adding objects to the control, so Key from the hashtable is meaningless as the ValueMember. Your post references a file variable in passing, so I will assume this revolves around showing the filename while wanting to get the full pathname when selected/clicked. WebForms/Winforms/WPF was not indicated, I am assuming WinForms:
Public Class myFile
Public Property FileName As String
Public Property FullPath As String
Public Property FileSize As Int64 ' just so there is something else
Public Sub New(f as String, p as String, s as Int64)
FileName = f
FullPath = b
FileSize = s
End Sub
End Class
Lets say we want to add some of these to a ListBox, for each item added we want FileName to display as the text, but want to get them back by FullPath:
Dim f As myFile
' assume these come from a fileinfo
For Each fi as FileInfo in DirectoryInfo.GetFiles(searchFor)
f = New myFile
f.FileName = fi.Name
f.FullPath = fi.FullPath
f.FileSize = fi.Length
' myFile accepts all the prop values in the constructor
' so creating a new one could also be written as:
' f = New myFile(fi.Name, fi.FullPath, fi.Length)
myListBox.Items.Add(f)
Next n
If the myFile objects were stored to a List(of myFile) rather than adding them to the control, we can bind the List as the DataSource and not have to iterate or copy:
mylistBox.DataSource = myFileList
Either way, Display- and ValueMember refer to the property names we wish to use:
myListBox.DisplayMember = "FileName" ' indicate property name of obj to SHOW
myListBox.ValueMember = "FullPath" ' prop name of object to return
When you select a listbox item, myListBox.SelectedValue would refer to the FullPath of the myFile object clicked on. The SelectedIndex would still refer to the index of the item in the list.
tl;dr
ValueMember and DisplayMember refers to the Property Names of Objects represented in the list.
Note:
I know this is many years later, but still relevant information.
It took me a while to parse what was said above, until I grokked it fully, so I thought it might help if I restated it slightly.
When you select a listbox item,
myListBox.SelectedValue is the contents of the field, myListBox.ValueMember. ValueMember contains the Field Name, SelectedValue contains the contents of the field.
myListBox.SelectedItem is the contents of the field myListBox.DisplayMember. DisplayMember contains the field name and SelectedItem contains the value of the field.
The SelectedIndex refers to the index of the item in the list. To see which item is selected, reference myListBox.SelectedIndex. You can, for example, change the selection to the last item in the list by using myListBox.SelectedIndex = myListBox.Items.Count - 1
If you want to display the values, then
Console.WriteLine("The value of {0} is {1}",myListBoxDisplayMember,myListBox.SelectedItem)
Console.WriteLine("The Value of {0} is {1}",myListBox.ValueMember,myListBox.SelectedValue)