vb.net fastest way to remove duplicates from listview? - vb.net

Does anyone have a quicker and better way to remove duplicates from a listview? I am currently doing it like this: I sort the items alphabetically, and then it checks the item below and compares it with the one above.
This is time consuming though.. When I enter 20.000 records into an excel sheet, and remove duplicates it takes a few miliseconds, but with this code below it takes hours to check 20.000 items in vb.net. Does anyone know of a faster method?
Dim max As Integer = ListView2.Items.Count
Dim i As Integer = 0
For Each item As ListViewItem In ListView2.Items
If i = max Then
Exit For
End If
If i > 0 Then
If item.Text = ListView2.Items(i - 1).Text Then
max -= 1
item.Remove()
i -= 1
End If
End If
i += 1
Label4.Text = "Total domains: " & ListView2.Items.Count
Next

Here is a Linq based solution to get distinct items based on item.Text and sort the items. If sorting is not required, you can remove the OrderBy part.
Private Shared Sub RemoveDuplicatesAndSort(lv As ListView)
Dim distictItems As ListViewItem() = lv.Items.Cast(Of ListViewItem)().Distinct(New LVItemComparer()).OrderBy(Function(item As ListViewItem) item.Text).ToArray
lv.BeginUpdate() ' suppress screen updates
lv.Items.Clear()
lv.Items.AddRange(distictItems)
lv.EndUpdate()
End Sub
Private Class LVItemComparer : Implements IEqualityComparer(Of ListViewItem)
Public Function Equals1(x As ListViewItem, y As ListViewItem) As Boolean Implements IEqualityComparer(Of ListViewItem).Equals
Return x.Text.Equals(y.Text)
End Function
Public Function GetHashCode1(obj As ListViewItem) As Integer Implements IEqualityComparer(Of ListViewItem).GetHashCode
Return obj.Text.GetHashCode
End Function
End Class

Use HashSet which will accept only unique values.
Dim itemsText = ListView2.Items.Cast(Of ListViewItem).Select(Function(item) item.Text)
Dim uniquesValues As HashSet(Of String) = New HashSet(Of String)(itemsText)
Then set items from HashSet to the ListView.

Related

VB.net - Sorting only one column in datagridview

I'm populating a DataGridView from an Excel file, and trying to sort only ONE column of my choice, other columns should remain as-is. How can it be achieved? Should the component be changed to something else, in case it is not possible in DataGridView?
I Created a List of my custom class, and this class will handle the sorting based on my preference (Randomization in this case)
Public Class Mylist
Implements IComparable(Of Mylist)
Private p_name As String
Private r_id As Integer
Public Property Pname() As String 'This will hold the contents of DGV that I want sorted
Get
Return p_name
End Get
Set(value As String)
p_name = value
End Set
End Property
Public Property Rid() As Integer 'This will be the basis of sort
Get
Return r_id
End Get
Set(value As Integer)
r_id = value
End Set
End Property
Private Function IComparable_CompareTo(other As Mylist) As Integer Implements IComparable(Of Mylist).CompareTo
If other Is Nothing Then
Return 1
Else
Return Me.Rid.CompareTo(other.Rid)
End If
End Function
End Class
Then a Button which will sort the contents:
Dim selcol = xlView.CurrentCell.ColumnIndex
Dim rand = New Random()
Dim x As Integer = 0
Dim plist As New List(Of Mylist)
Do While x < xlView.Rows.Count
plist.Add(New Mylist() With {
.Pname = xlView.Rows(x).Cells(selcol).Value,
.Rid = rand.Next()
})
x += 1
Loop
plist.Sort()
x = 0
Do While x < xlView.Rows.Count
xlView.Rows(x).Cells(selcol).Value = plist.ElementAt(x).Pname
x += 1
Loop
xlView.Update()
plist.Clear()
I'm open to any changes to code, as long as it achieves the same result.
Here is the simpler version. Pass the column index will do like Call SortSingleColum(0)
Private Sub SortSingleColumn(x As Integer)
Dim DataCollection As New List(Of String)
For i = 0 To dgvImport.RowCount - 2
DataCollection.Add(dgvImport.Item(x, i).Value)
Next
Dim t As Integer = 0
For Each item As String In DataCollection.OrderBy(Function(z) z.ToString)
dgvImport.Item(x, t).Value = item
t = t + 1
Next
End Sub

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.

Generate a property name in a loop

I'm trying to find a way of loading a single record with 25 columns into a datatable.
I could list all 25 variables called SPOT1 to SPOT25 (columns in the datatable) but I'm looking for more concise method like using a loop or dictionary.
The code below shows two methods, a 'long' method which is cumbersome, and a 'concise' method which I'm trying to get help with.
Public dctMC As Dictionary(Of String, VariantType)
Dim newMC As New MONTE_CARLO()
'long method: this will work but is cumbersome
newMC.SPOT1=999
newMC.SPOT2=887
...
newMC.SPOT25=5
'concise method: can it be done more concisely, like in a loop for example?
Dim k As String
For x = 1 To 25
k = "SPOT" & CStr(x)
newMC.K = dctMC(k) 'convert newMC.k to newMC.SPOT1 etc
Next
'load record
DATA.MONTE_CARLOs.InsertOnSubmit(newMC)
Per the others, I think there are better solutions, but it is possible...
Public Class MONTE_CARLO
Private mintSpot(-1) As Integer
Property Spot(index As Integer) As Integer
Get
If index > mintSpot.GetUpperBound(0) Then
ReDim Preserve mintSpot(index)
End If
Return mintSpot(index)
End Get
Set(value As Integer)
If index > mintSpot.GetUpperBound(0) Then
ReDim Preserve mintSpot(index)
End If
mintSpot(index) = value
End Set
End Property
End Class
Usage...
Dim newMC As New MONTE_CARLO
For i As Integer = 0 To 100
newMC.Spot(i) = i
Next i
MsgBox(newMC.Spot(20))

More effective loop

I'm making a player-match-up program in Visual Basic. The program is supposed to pick random registered players and and pair them. I'm currently working on the odd-number-of-players-part.
The solution I have is working but is perhaps not that effective. Is there a better way for me to write this code?
The code is supposed to pick the random players and make sure they are not picked again. As you see, for the code to work i must make it loop thousands of times. If I don't some of the players won't show up in the listbox. Is there a better solution???
In case it's confusing "spiller" is norwegian for "player"
For i As Integer = 0 To 100000
Dim spiller1 As Integer
Dim spiller2 As Integer
Do
spiller1 = CInt(Math.Floor(Rnd() * spillerListe.Count))
spiller2 = CInt(Math.Floor(Rnd() * spillerListe.Count))
Loop Until CBool(spiller1 <> spiller2)
If brukteSpillere(spiller1) = False And brukteSpillere(spiller2) = False Then
brukteSpillere(spiller1) = True
brukteSpillere(spiller2) = True
lstSpillere.Items.Add(spillerListe(spiller1).ToString + " VS. " + spillerListe(spiller2).ToString())
End If
Next i
This is a mess... Have a List(Of Integer) with all the available index.
Loop while availableIndex.Count > 1
Pick a random index from availableIndex and remove it from that list
Pick a random index from availableIndex and remove it from that list
Add these two index to the list of pairs
End Loop
that way you don't need to check if the random values are the same or if they were already picked.
Now, if you don't want to create a list. Then threat the random number not as an index, but as the number of items to check.
Delta = RandomNumber
x = 0
For i As Integer = 0 To itemList.Count-1
If Not itemList(i).IsChoosen Then
x += 1
If x = Delta Then
' i is now your item to pick
itemList(i).IsChoosen = True
Exit For
End If
End If
Next
There are two efficient ways in approaching this problem:
Sort your player list by random number, then match up 1 with 2, 3 with 4 and so on.
Dim r As New Random
Dim randomListe = spillerListe.OrderBy(Function() r.Next).ToList
Generate two random numbers from your range, match up those players into a separate List, remove players from the original list. General two more random numbers from a smaller range (original minus 2), match up, etc.
EDIT: Having looked at MSDN, List has O(n) performance for RemoveAt, so it's not quite efficient, better be using a dictionary, which is O(1) at removing items, so instead of spillerListe have some spillerDicte, where you would add entries in a form (key = index, value = item).
Instead of working with integers, what if you keep your players name in a list and, after picking a player you remove it from the list. Probably this will not be the best performant solution, but it is clear what are you trying to do
Dim lstSpillere = new List(Of String)() ' Just for the example below
Dim spillerListe = new List(Of String)() from {"Marc", "John", "Steve", "George", "David", "Jeremy", "Andrew" }
Dim rnd = new Random()
While spillerListe.Count > 1
Dim firstPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(firstPlayer)
Dim secondPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(secondPlayer)
lstSpillere.Add(firstPlayer + " VS. " + secondPlayer)
' for debug purpose....
Console.WriteLine(firstPlayer & " VS. " & secondPlayer)
End While
if spillerListe.Count > 0 Then
Console.WriteLine("Excluded from play list is:" & spillerListe(0))
End if
The important key here is the generation of Random instance that should be outside the loop to avoid to generate the same number in the short time period required by the loop to execute.
Try this:
Module Module1
Dim rnd As New Random
Sub Main()
Dim RegisteredPlayers As New List(Of Player)
' Fill List (example 100 players)
For index As Integer = 1 To 100
RegisteredPlayers.Add(New Player(String.Format("Player{0}", index)))
Next
'Sort Players using a random number
Dim SortedPlayersArray = RandomSortItems(RegisteredPlayers.ToArray())
'Pair players by selecting 2 consequative ones from randomly sorted array
Dim Matches As New List(Of Match)
For index As Integer = 1 To SortedPlayersArray.Length Step 2
Dim m As Match = New Match(SortedPlayersArray(index - 1), SortedPlayersArray(index))
Matches.Add(m)
Debug.WriteLine(m.ToString())
Next
' Match Player48 vs. Player79
' Match Player3 vs. Player53
' Match Player18 vs. Player43
' Match Player85 vs. Player1
' Match Player47 vs. Player56
' Match Player23 vs. Player66
' etc..
End Sub
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim sorted As T() = New T(items.Length-1) {}
Array.Copy(items, sorted, sorted.Length)
Dim keys As Double() = New Double(items.Length-1) {}
For i As Integer = 1 To items.Length
keys(i - 1) = rnd.NextDouble()
Next
Array.Sort(keys, sorted)
Return sorted
End Function
End Module1
Public Class Player
Dim m_name As String
Public Sub New(ByVal player_name As String)
m_name = player_name
End Sub
Public ReadOnly Property Name() As String
Get
Return m_name
End Get
End Property
Public Overrides Function ToString() As String
Return m_name
End Function
End Class
Public Class Match
Dim m_player_1 As Player, m_player_2 As Player
Public Sub New(ByVal player_1 As Player, ByVal player_2 As Player)
m_player_1 = player_1
m_player_2 = player_2
End Sub
Public ReadOnly Property Player1() As Player
Get
Return m_player_1
End Get
End Property
Public ReadOnly Property Player2() As Player
Get
Return m_player_2
End Get
End Property
Public Overrides Function ToString() As String
Return String.Format("Match {0} vs. {1}", Player1, Player2)
End Function
End Class
Edit 1:
An alternate random sorter (which should be faster) is
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim slist As New SortedList(Of Double, T)
For i As Integer = 1 to items.Length
slist.Add(rnd.NextDouble(), items(i-1) )
Next i
return slist.Values.ToArray()
End Function

How to sort lisbox Items by item length vb.net

if there is any method in vb.net to sort a listbox by item length e.g
there is a property in listbox to sort items alphabetically
listbox1.sorted = true
but how to sort by item length
You can try:
listbox1.DataSource = listbox1.Items.Cast(Of Object).OrderByDescending(Function(x) x.ToString.Length).ToList
My mistake! This is for a ListView and not a ListBox. Here is an example (C#) for a listbox custom sort.
You can assign an IComparer class to the ListViewItemSorter of the listview to compare the items based on length. This is a little cumbersome, but it gives you complete control over the sort order.
Here's an example (not tested. uColumn is the sort column):
ListView1.ListViewItemSorter = New lvSort
...
Class lvSort
Implements IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _
Implements IComparer.Compare
s1 = CType(x, ListViewItem).SubItems(uColumn).Text
s2 = CType(y, ListViewItem).SubItems(uColumn).Text
If s1.Length < s2.Length Then
Return (-1)
ElseIf s1.Length > s2.Length Then
Return (1)
Else
Return (0)
End If
End Function
End Class