2010-06-04 8 views
0

J'écris une application pour comparer chaque élément de listbox1 à tous les éléments de listbox2. Si l'élément est trouvé, supprimez-le des deux listes. Le but est que les éléments qui n'ont pas été trouvés restent sur les deux listes.L'application VB.NET 2008 plante pendant Do Loop

Le problème est, l'application se bloque et je n'obtiens jamais aucun résultat. J'ai regardé mon code plusieurs fois et je n'arrive pas à comprendre ce qui se passe (programmation noob je sais ...).

Quelqu'un peut-il m'aider avec ça?

Code Snippet:

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click 

    Dim a As String 
    Dim b As String 
    Dim y As String 

    For i As Integer = 0 To ListBox1.Items.Count - 1 
     a = ListBox1.Items(i) 
     y = 1 
     Do While y = 1 
      For x As Integer = 0 To ListBox2.Items.Count - 1 
       b = ListBox2.Items(x) 
       Dim res As Int16 = String.Compare(a, b) 
       If res = 0 Then 
        y = 0 
        ListBox2.Items.Remove(i) 
        ListBox2.Items.Remove(x) 
       ElseIf x = ListBox1.Items.Count Then 
        Exit Do 
       End If 
      Next 
     Loop 
    Next 
End Sub 
+0

Que se passe-t-il si l'une des zones de liste contient des dupes dans la zone de liste? LB1 = ABCA, LB2 = zBCDEFz. Quels devraient être les résultats? Voir le code de test. – dbasnett

Répondre

1

si ListBox1.Items.Count est plus que ListBox2.Items.Count - 1, X ne sera jamais égale ListBox1.Items.Count, de sorte que la sortie Do ne fonctionnera jamais, et le code tournera sans fin dans le

Do While y = 1 

Avez-vous envisagé d'utiliser Linq par exemple, pour faciliter la gestion des listes?

edit: En outre, il est faux de supprimer un élément de la liste que vous parcourez avec un (il est carrément illégal de le faire avec For Each) car chaque suppression va décaler le compteur de boucle.

Edit2: voici un extrait Linq qui accomplit la tâche:

Dim itemsFirst = (From item As String In ListBox1.Items Select item) 
    Dim itemsSecond = (From item As String In ListBox2.Items Select item) 

    Dim dupes = System.Linq.Enumerable.Intersect(itemsFirst, itemsSecond).ToList 
    For Each item In dupes 
     ListBox1.Items.Remove(item) 
     ListBox2.Items.Remove(item) 
    Next item 

ce qui est fait est d'extraire essentiellement les chaînes à la fois la liste (ce qui est nécessaire parce que la collection ListBox.Items est un peu bizarre)

Ensuite, nous exécutons la méthode d'intersection et copions les résultats dans une liste. (la partie .ToList) La copie est une partie obligatoire car, sinon, les dupes ne seraient qu'un sous-ensemble des Items de la ListBox, et encore une fois nous essayerions de nous soulever en tirant sur nos chaussures.

La dernière partie est une simple boucle de suppression, qui supprime les éléments de la collection

+0

J'ai parcouru autour et suis tombé sur cette page http://msdn.microsoft.com/fr-fr/netframework/aa904594.aspx Est-ce que LINQ est un module téléchargeable? –

+0

Pas vraiment, c'est plutôt une fonctionnalité de langage qui simplifie la gestion des données. Il est disponible à partir de VS2008 vers le haut – SWeko

+0

Ajout d'une solution basée sur LINQ, essentiellement une version modifiée et expliquée de la solution Joel Coehoorn – SWeko

2

vous avez

ElseIf x = ListBox1.Items.Count Then 
    Exit Do 

quand il devrait être

ElseIf x = ListBox1.Items.Count - 1 Then 
    Exit Do 

parce que votre boucle for changez X pour compter, puis quittez sans itération à cette valeur.

Non seulement cela, mais pourquoi y at-il une boucle Do de toute façon? Il n'y a pas besoin de continuer à parcourir la même liste de recherche intérieure pour trouver des doublons, n'est-ce pas? Et troisièmement, vous ne devriez pas supprimer des choses pendant que vous les parcourez. Dans votre cas, les boucles for réutilisent count, donc c'est "safe" mais l'opération remove va réindexer les choses, donc vous devriez soustraire 1 à vos i et x itérateurs lorsque vous supprimez, de sorte que le suivant ne soit pas ignoré par la réindexation .

A la réflexion, peut-être que vous mettez cette Do en boucle pour couvrir les éléments ignorés la fois précédente, comme mentionné dans mon troisième point.

+0

J'ai essayé mais le problème persiste. Et je vois ce que vous voulez dire par le nombre de x qui change. Et oui, j'ai gardé la boucle Do là-dedans, donc elle couvre l'intégralité de l'index à chaque fois (la performance n'est pas vraiment un problème, la liste ne fait que 2500 lignes.) –

1

Si vous utilisez Visual Studio 2008 ou plus tard:

Dim dupes = Listbox1.Items.Cast(Of String)().Intersect(Listbox2.Items.Cast(Of String)()).ToArray() 
For Each item As String in dupes 
    Listbox1.Items.Remove(item) 
    Listbox2.Items.Remove(item) 
Next item 
+0

Malheureusement, je n'ai accès qu'à Visual Basic 2008 Express Edition donc je suppose que c'est pourquoi je ne peux pas voir la fonction. –

+0

@RedHaze qui devrait gérer cela très bien. Cependant, vous aurez besoin d'un système Imports.Linq en haut. –

+0

Nous vous remercions de votre patience! J'ai ajouté les importations en haut et je reçois toujours: 'Intersect' n'est pas un membre de System.Windows.Forms.ListBox.ObjectCollection ' –

0

j'ai couru un test de trois méthodes différentes. Ils sont Joels, sweko et les miens. Je faisais cela pour tester la performance, mais j'ai découvert que les résultats ne sont pas les mêmes, les boîtes de liste ne sont pas les mêmes. Voici le code que j'ai utilisé pour tester, donc vous pouvez être le juge. Probablement une erreur stupide de ma part.

Dim stpw As New Stopwatch 

Private Sub Button1_Click(ByVal sender As System.Object, _ 
          ByVal e As System.EventArgs) Handles Button1.Click 
    Debug.WriteLine("") 
    loadLBTD() ''#load test data 

    doSTPW("Test 1 Start", False) ''#mine 
    ''#get rid of dupes <<<<<<<<<<<<<< 
    Dim dupeL As New List(Of String) 
    For x As Integer = ListBox1.Items.Count - 1 To 0 Step -1 
     If ListBox2.Items.Contains(ListBox1.Items(x)) Then 
      dupeL.Add(ListBox1.Items(x)) 
      ListBox1.Items.RemoveAt(x) 
     End If 
    Next 
    For Each s As String In dupeL 
     ListBox2.Items.Remove(s) 
    Next 
    doSTPW("Test 1 End") 

    loadLBTD() ''#load test data 

    doSTPW("Test 2 Start", False) ''#sweko 
    ''#get rid of dupes <<<<<<<<<<<<<< 
    ''#I had to set Option Strict to Off to get this to work <<<<<<< 
    Dim itemsFirst = (From item As String In ListBox1.Items Select item) 
    Dim itemsSecond = (From item As String In ListBox2.Items Select item) 

    Dim dupes = System.Linq.Enumerable.Intersect(itemsFirst, itemsSecond).ToList 
    For Each item In dupes 
     ListBox1.Items.Remove(item) 
     ListBox2.Items.Remove(item) 
    Next item 
    doSTPW("Test 2 End") 

    loadLBTD() ''#load test data 

    doSTPW("Test 3 Start", False) ''#joel 
    ''#get rid of dupes <<<<<<<<<<<<<< 
    Dim dupes2 = ListBox1.Items.Cast(Of String)().Intersect(ListBox2.Items.Cast(Of String)()).ToArray() 
    For Each item As String In dupes2 
     ListBox1.Items.Remove(item) 
     ListBox2.Items.Remove(item) 
    Next item 
    doSTPW("Test 3 End") 
End Sub 

Private Sub doSTPW(ByVal someText As String, Optional ByVal showTM As Boolean = True) 
    stpw.Stop() ''#stop the clock 
    If flip Then Debug.Write("'T ") Else Debug.Write("'F ") 
    Debug.Write("LBCnts " & ListBox1.Items.Count & " " & ListBox2.Items.Count) 
    Dim s As String 
    If showTM Then 
     s = String.Format(" {0} {1}", someText, stpw.ElapsedTicks.ToString("N0")) 
    Else 
     s = String.Format(" {0}", someText) 
    End If 
    Debug.WriteLine(s) 
    stpw.Reset() ''#reset and start clock 
    stpw.Start() 
End Sub 

Dim flip As Boolean = False 
Private Sub loadLBTD() 
    ''#Create test data 
    Dim tl1() As String = New String() {"A", "X", "y", "z", "B", "w", "X", "y", "z"} 
    Dim tl2() As String = New String() {"A", "y", "z", "Q", "A", "y", "z", "Q", "A", "y", "z", "Q"} 
    ListBox1.Items.Clear() 
    ListBox2.Items.Clear() 
    ''#load listboxes 
    If flip Then 
     ListBox1.Items.AddRange(tl2) 
     ListBox2.Items.AddRange(tl1) 
    Else 
     ListBox1.Items.AddRange(tl1) 
     ListBox2.Items.AddRange(tl2) 
    End If 
    ''#end of test data setup 
End Sub 

De même, comme prévu, LINQ est plus concis mais plus lent. Si le code est rarement utilisé, cela n'a pas d'importance. J'ai eu une mauvaise expérience avec LINQ et un tamis d'Eratosthenes.

+0

Je m'attendrais à ce que le mien soit plus lent car je devine le type pour l'appel à Cast (). Quant à linq, il peut être très rapide s'il est utilisé correctement, mais il faut faire attention. Chaque fois que vous vous appelez .ToList() ou .ToArray() vous tuez les performances. Dans ce cas, il est nécessaire d'éviter une condition de concurrence potentielle. –

+0

@Joel Coehoorn - qu'en est-il des résultats différents? C'est juste moi, mais je n'ai jamais vu un cas où LINQ était plus rapide pour ce genre de chose. – dbasnett

+0

Vous avez raison. Le code sera utilisé deux fois par mois avec un ensemble de données de moins de 10.000 enregistrements au total. Les tests que j'ai effectués avec environ 3000 enregistrements ont pris environ 3 secondes, ce qui surpasse les anciennes méthodes qu'ils utilisaient (comparaison manuelle). Je ne m'attendais pas à beaucoup d'aide de votre part, les commentaires ont été incroyables. Merci beaucoup. –