[vb.net] List(of T) miteinander Vergleichen

Gepostet am: Jun 13, 2011 7:29:40 PM

Ausgangsbasis ist eine sauber erstellte Klasse:

Public Class Auto

Public Class Auto     Private _marke As String = String.Empty     Private _modell As String = String.Empty      Public Sub New(ByVal marke As String, ByVal modell As String)         Me._marke = marke         Me._modell = modell     End Sub      Public Property Marke() As String         Get             Return _marke         End Get         Set(ByVal value As String)             Me._marke = value         End Set     End Property      Public Property Modell() As String         Get             Return _modell         End Get         Set(ByVal value As String)             Me._modell = value         End Set     End PropertyEnd Class

Hat man nun zwei Listen dieser Klasse und möchte diese miteinander vergleichen, gibt es verschiedene Möglichkeiten, mit unterschiedlichen Ergebnis.

Vergleich der Referenz

Vergleich mit .Contains

Will man aus den 2 Listen eine weitere Liste erstellen, die nur die Elemente enthält, die in beiden vorkommen, gibt es folgende Möglichkeit:

Compare_with_Contains

Dim Autos3 As New List(Of Auto)For Each myAuto As Auto In Autos2    If Autos1.Contains(myAuto) Then       Autos3.Add(myAuto)    End IfNext

Als etwas schönerer generische Funktion:

Textfeld

Private Function Compare2List_Contains(Of mytype)(ByVal lst1 As List(Of mytype), ByVal lst2 As List(Of mytype)) As List(Of mytype)    Dim lst3 As New List(Of mytype)    For Each Element As mytype In lst2        If lst1.Contains(Element) Then           lst3.Add(Element)        End If    Next    Return lst3 End Function

Das funktioniert aber nur unter folgender Voraussetzung: 

funktioniert

Dim myAuto As New Auto("BMW", "X3") Autos1.Add(myAuto) Autos2.Add(myAuto)

nicht jedoch, wenn Elemente folgendermaßen hinzugefügt werden:

funktioniert nicht:

Autos1.Add(New Auto("Volkswagen", "Passat")) Autos2.Add(New Auto("Volkswagen", "Passat"))

Das ist jedoch keine zielführende Möglichkeit, wenn man die Listen nach deren Inhalt und nicht nach deren Referenz vergleichen möchte.

Vergleich mit .GetHashCode

Eine andere Methode, wäre über den Vergleich der Elemente mittels .GetHashCode. Dise funktioniert ebenfalls nur bei identischen Referenzen. Als generische Funktion, würde das dann beispielsweise so aussehen:

Compare2List_GetHashCode

Private Function Compare2List_GetHashCode(Of myType)(ByVal lst1 As List(Of myType), ByVal lst2 As List(Of myType)) As List(Of myType)    Dim lst3 As New List(Of myType)    For Each Element As myType In lst1        Dim tmpElement As myType = Element        If lst2.Exists(Function(x As myType) x.GetHashCode = tmpElement.GetHashCode) Then           lst3.Add(Element)        End If    Next    Return lst3 End Function

Vergleich der Inhalte

Einen etwas anderen Weg muss man beschreiten, wenn man die Inhalte vergleichen möchte und nicht nur die Referenz. Eine mit Contains vergleichbare Funktion gibt es nicht. 

Vergleich mit .Exists

Das Grundkonzept funktioniert folgendermaßen:

Grundkonzept

Dim Autos3 As New List(Of Auto)   For Each myAuto2 As Auto In Autos2  Dim myAuto2tmp As Auto = myAuto2  If Not Autos1.Exists(Function(x As Auto) x.Marke = myAuto2tmp.Marke And x.Modell = myAuto2tmp.Modell) Then     Autos3.Add(myAuto2)   End IfNext

Will man das in eine Funktion packen, steht man jedoch vor dem Problem, dass man nicht weiß welche Properties der Klasse verglichen werden sollen. In diesem Fall muss man die Klasse um eine Overrides Function erweitern, die beispielsweise .toString oder .GetHashCode ersetzt.

Die Klasse Auto, erweitert um ToString als Overrides Function müsste folgendermaßen aussehen:

überarbeitete Klasse

Public Class Auto     Private _marke As String = String.Empty     Private _modell As String = String.Empty      Public Sub New(ByVal marke As String, ByVal modell As String)         Me._marke = marke         Me._modell = modell     End Sub      Public Property Marke() As String         Get             Return _marke         End Get         Set(ByVal value As String)             Me._marke = value         End Set     End Property      Public Property Modell() As String         Get             Return _modell         End Get         Set(ByVal value As String)             Me._modell = value         End Set     End Property      Public Overrides Function ToString() As String         Return LCase(Me._marke) & "|" & LCase(Me._modell)     End FunctionEnd Class

Dann könnte man beispielsweise folgende generische Funktion zum Vergleich verwenden. 

Compare2List_Exists

Private Function Compare2List_Exists(Of myType)(ByVal lst1 As List(Of mytype), ByVal lst2 As List(Of mytype)) As List(Of mytype)   Dim lst3 As New List(Of myType)   For Each Element As myType In lst1       Dim tmpElement As myType = Element       If lst2.Exists(Function(x As myType) x.ToString = tmpElement.ToString) Then          lst3.Add(Element)       End If   Next   Return lst3 End Function

Ausgangslisten bereinigen

Spinnen wir den Gedanken noch ein Stück weiter. Die 2 Listen sollen nun nicht mehr nur auf in beiden vorkommenden Elemente untersucht werden, sondern es sollen auch in beiden Listen, die Elemente, die in beiden Listen vorkommen, entfernt werden.

Geht man davon aus, dass in Liste1 alle Elemente nur einmal vorkommen, in Liste2 es jedoch auch möglich sein kann, dass Elemente mehrfach vorkommen, könnte man die Listen folgendermaßen bereinigen:

Compare2List_andRemove

Private Function Compare2List_andRemove(Of myType)(ByRef lst1 As List(Of myType), ByRef lst2 As List(Of myType)) As List(Of myType)    Dim lst3 As New List(Of myType)    Dim i As Integer = 0    Dim foundItems As New List(Of myType)    Do      Dim tmpElement1 As myType = lst1(i)      foundItems = lst2.FindAll(Function(x As myType) x.ToString = tmpElement1.ToString)      If foundItems.Count <> 0 Then         lst3.Add(tmpElement1)         lst1.RemoveAt(i)         For Each founditem As myType In foundItems             lst2.Remove(founditem)         Next         foundItems.Clear()     Else       i = i + 1     End If   Loop Until (i = (lst1.Count))   Return lst3 End Function

Wenn man davon ausgeht, dass sowohl Liste1 als auch Liste2 Elemente mehrfach enthält, dann könnte man folgendermaßen vorgehen:

Compare2List_andRemove

Private Function Compare2List_andRemove(Of myType)(ByRef lst1 As List(Of myType), ByRef lst2 As List(Of myType)) As List(Of myType)    Dim lst3 As New List(Of myType)    Dim i As Integer = 0    Dim foundItems As New List(Of myType)    For Each element As myType In lst1        Dim tmpElement1 As myType = element        foundItems = lst2.FindAll(Function(x As myType) x.ToString = tmpElement1.ToString)        If foundItems.Count <> 0 Then           lst3.Add(tmpElement1)           For Each founditem As myType In foundItems               lst2.Remove(founditem)           Next           foundItems.Clear()        End If    Next      For Each element As myType In lst3        Dim tmpElement1 As myType = element        foundItems = lst1.FindAll(Function(x As myType) x.ToString = tmpElement1.ToString)        If foundItems.Count <> 0 Then           For Each founditem As myType In foundItems               lst1.Remove(founditem)           Next           foundItems.Clear()        End If    Next        Return lst3 End Function

Dublikate aus Liste entfernen

Selbiges könnte man auch auf eine einzelne List(of T) anwenden, um die Dublikate der Liste zu entfernen.

Liste bereinigen

Private Sub ListeBereinigen(Of myType)(ByRef lst As List(Of myType))    Dim Lst_of_Dublicates As New List(Of myType)    For Each element As myType In lst        Dim tmpElement As myType = element        Dim foundItems As List(Of myType) = lst.FindAll(Function(x As myType) x.ToString = tmpElement.ToString)        If foundItems.Count > 1 Then           Lst_of_Dublicates.Add(tmpElement)        End If    Next    For Each element As myType In Lst_of_Dublicates        Dim tmpElement As myType = element        Dim foundItems As List(Of myType) = lst.FindAll(Function(x As myType) x.ToString = tmpElement.ToString)        If foundItems.Count <> 0 Then           Dim i As Integer = 0           For Each founditem As myType In foundItems               If i <> 0 Then                  lst.Remove(founditem)               End If               i = i + 1           Next        End If   NextEnd Sub

Download

Um die Wirkungsweise der Funktionen zu veranschaulichen, stelle ich den SourceCode und das Programm hier zum Download zur Verfügung. (siehe Anhänge)