views:

58

answers:

4

I am writing an application to compare each item on listbox1 to all items on listbox2. If the item is found, then remove it from both lists. The goal is to only have the items that were not found remain on both lists.

The problem is, the application just hangs and I never get any results. I looked at my code several times and I cannot figure out what's going on (programming noob I know...).

Can anybody help me with this?

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
+1  A: 

if ListBox1.Items.Count is more that ListBox2.Items.Count - 1, X will never equal ListBox1.Items.Count, so the Exit Do will never run, and the code will just loop endlessly in the

Do While y = 1

Have you considered using Linq for example, for easier list management?

edit: Additionally it's wrong to delete an item from the list you are traversing with a for (it's downright illegal to do that with a For Each) because each deletion will offset the loop counter.

edit2: here's a Linq snippet that accomplishes the task:

    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

what is does is basically extract the strings from both list (this is necessary because the ListBox.Items collection is a little weird)

After that we run the intersect method and copy the results into a list. (the .ToList part) The copying is a required part because, otherwise dupes would just be a subset of the Items of the ListBox, and once again we would be trying to lift ourselves by pulling on our shoestrings.

The last part is just a simple delete loop, that removes the items from the collection

SWeko
I browsed around and came across this page http://msdn.microsoft.com/en-us/netframework/aa904594.aspxIs LINQ a downloadable add-on?
RedHaze
Not really, it's more of a language feature that simplifies data management. It's available from VS2008 upwards
SWeko
Added a LINQ-based solution, basically a modified and explained version of Joel Coehoorn solution
SWeko
Works beautifully! You sir are amazing! I will go ahead and research more about LINQ. I can't believe this could be done with such little code.
RedHaze
@SWeko - Does this work? See my test below. It may just be my test.
dbasnett
@SWeko: wht .ToList if all you're doing is iterating?
John Saunders
+2  A: 

you have

ElseIf x = ListBox1.Items.Count Then
    Exit Do

when it should be

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

because your for loop will change X to count, and then exit without iterating at that value.

Not only that, but why is there a Do loop anyway? There's no need to keep iterating the same inner listbox looking for duplicates, is there?

And thirdly, you shouldn't remove things while you iterate through them. In your case the for loops are reusing count, so it's "safe" but the remove operation will reindex things, so you should subtract 1 from your i and x iterators when you remove, so that the next one isn't skipped by the reindexing.

On second thought, maybe you put that Do loop in there to cover the elements skipped the previous time around, as mentioned in my third point.

Tesserex
I tried that but the problem still persists. And I see what you mean by the x count changing. And yes, I kept the Do loop in there so it actually covers the entire index every time (performance isn't much of an issue. The list is only ~2500 lines long.)
RedHaze
+1  A: 

If you're using visual studio 2008 or later:

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
Joel Coehoorn
Unfortunately I only have access to Visual Basic 2008 Express Edition so I guess that's why I can't see the function.
RedHaze
@RedHaze that should handle this just fine. You will need an Imports System.Linq at the top, though.
Joel Coehoorn
Thank you for your patience! I added the imports at the top and I still receive: 'Intersect' is not a member of System.Windows.Forms.ListBox.ObjectCollection'
RedHaze
@RedHaze that's because it's not. It's an _extension_ for IEnumerable<T>. Unfortunately, it looks like ObjectCollection only implements IEnumerable with no type specifier. That means you'll have to cast. I updated my answer, but I had to guess at the actual type stored in the listbox.
Joel Coehoorn
@Joel Coehoorn - Does this work? See my test below. It may just be my test.
dbasnett
A: 

I ran a test of three different methods. They are Joels, sweko, and mine. I was doing this to test performance, but I found out the results are not the same, the listboxes aren't the same. Here is the code I used to test, so you can be the judge. Probably some dumb mistake on my 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

Also, as expected, LINQ is more concise but slower. If the code is used infrequently it doesn't matter. I had a bad experience with LINQ and a Sieve of Eratosthenes.

dbasnett
I would expect mine to be slower because I'm guessing the type for the call to Cast<string>(). As for linq, it can be _very_ fast if used correctly, but you have to be careful. Any time you find yourself calling .ToList() or .ToArray() you're killing performance. In this case it's necessary to avoid a potential race condition.
Joel Coehoorn
@Joel Coehoorn - what about the results being different? This is just me, but I have never seen a case where LINQ was faster for this kind of thing.
dbasnett
You're right. The code will be used twice a month with a set data of under 10,000 records total. The tests I ran with around 3000 or so records took around 3 seconds which sure beats the old methods they were using (Manual comparison). I wasn't expecting so much great help from you guys, the feedback has been nothing short of amazing. Thank you very much.
RedHaze