Сбой приложения VB.NET 2008 во время выполнения цикла

Я пишу приложение для сравнения каждого элемента списка listbox1 со всеми элементами списка listbox2. Если элемент найден, то удалите его из обоих списков. Цель состоит в том, чтобы в обоих списках оставались только те элементы, которые не были найдены.

Проблема в том, что приложение просто зависает, и я никогда не получаю никаких результатов. Я несколько раз просматривал свой код и не мог понять, что происходит (нуб-программист, которого я знаю...).

Может ли кто-нибудь помочь мне с этим?

Фрагмент кода:

    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

person Rafael Sampaio    schedule 04.06.2010    source источник
comment
Что делать, если в любом списке есть дубликаты? LB1 = ABCA, LB2 = zBCDEFz. Какими должны быть результаты? См. тестовый код.   -  person dbasnett    schedule 05.06.2010


Ответы (4)


если ListBox1.Items.Count больше, чем ListBox2.Items.Count - 1, X никогда не будет равно ListBox1.Items.Count, поэтому Exit Do никогда не запустится, и код просто будет бесконечно зацикливаться в

Do While y = 1

Рассматривали ли вы, например, возможность использования Linq для упрощения управления списками?

редактировать: Кроме того, неправильно удалять элемент из списка, который вы просматриваете, с помощью for (совершенно незаконно делать это с помощью For Each), потому что каждое удаление будет смещать счетчик цикла.

edit2: вот фрагмент Linq, который выполняет задачу:

    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

то, что он делает, в основном извлекает строки из обоих списков (это необходимо, потому что коллекция ListBox.Items немного странная)

После этого мы запускаем метод пересечения и копируем результаты в список. (часть .ToList) Копирование является обязательной частью, потому что в противном случае дубликаты были бы просто подмножеством элементов ListBox, и мы снова попытались бы подняться, потянув за шнурки.

Последняя часть представляет собой простой цикл удаления, который удаляет элементы из коллекции.

person SWeko    schedule 04.06.2010
comment
Я просмотрел и наткнулся на эту страницу msdn.microsoft.com/en-us/ netframework/aa904594.aspx Является ли LINQ загружаемым дополнением? - person Rafael Sampaio; 05.06.2010
comment
Не совсем, это скорее языковая функция, упрощающая управление данными. Он доступен с VS2008 и выше. - person SWeko; 05.06.2010
comment
Добавлено решение на основе LINQ, в основном модифицированная и объясненная версия решения Joel Coehoorn. - person SWeko; 05.06.2010
comment
Красиво работает! Вы, сэр, удивительны! Я продолжу и буду больше изучать LINQ. Я не могу поверить, что это можно сделать с таким небольшим кодом. - person Rafael Sampaio; 05.06.2010
comment
@SWeko - это работает? Смотрите мой тест ниже. Это может быть просто мой тест. - person dbasnett; 05.06.2010
comment
@SWeko: что .ToList, если все, что вы делаете, это итерация? - person John Saunders; 05.06.2010

у тебя есть

ElseIf x = ListBox1.Items.Count Then
    Exit Do

когда это должно быть

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

потому что ваш цикл for изменит X на count, а затем выйдет без повторения этого значения.

И не только это, но почему вообще существует цикл Do? Нет необходимости повторять один и тот же внутренний список в поисках дубликатов, не так ли?

И в-третьих, вы не должны удалять вещи, пока вы их перебираете. В вашем случае циклы for повторно используют count, поэтому это «безопасно», но операция удаления будет переиндексировать вещи, поэтому вы должны вычесть 1 из ваших итераторов i и x при удалении, чтобы следующий не был пропущен при переиндексации .

Если подумать, может быть, вы поместили туда этот цикл Do, чтобы покрыть элементы, пропущенные в предыдущий раз, как упоминалось в моем третьем пункте.

person Tesserex    schedule 04.06.2010
comment
Я пробовал это, но проблема все еще сохраняется. И я понимаю, что вы имеете в виду под изменением числа x. И да, я оставил там цикл Do, так что он фактически каждый раз покрывает весь индекс (производительность не имеет большого значения. Список составляет всего около 2500 строк). - person Rafael Sampaio; 05.06.2010

Если вы используете Visual Studio 2008 или более позднюю версию:

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
person Joel Coehoorn    schedule 04.06.2010
comment
К сожалению, у меня есть доступ только к Visual Basic 2008 Express Edition, поэтому я не вижу этой функции. - person Rafael Sampaio; 05.06.2010
comment
@RedHaze, который отлично с этим справится. Однако вам понадобится Imports System.Linq вверху. - person Joel Coehoorn; 05.06.2010
comment
Спасибо за ваше терпение! Я добавил импорт вверху и все еще получаю: «Пересечение» не является членом System.Windows.Forms.ListBox.ObjectCollection. - person Rafael Sampaio; 05.06.2010
comment
@RedHaze, потому что это не так. Это расширение для IEnumerable‹T›. К сожалению, похоже, что ObjectCollection реализует только IEnumerable без спецификатора типа. Это означает, что вам придется бросать. Я обновил свой ответ, но мне пришлось угадать фактический тип, хранящийся в списке. - person Joel Coehoorn; 05.06.2010

Я проверил три разных метода. Это Джоэлс, Свеко и мой. Я делал это для проверки производительности, но обнаружил, что результаты разные, списки разные. Вот код, который я использовал для тестирования, так что вы можете судить. Наверное, какая-то глупая ошибка с моей стороны.

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

Кроме того, как и ожидалось, LINQ более лаконичен, но медленнее. Если код используется нечасто, это не имеет значения. У меня был неудачный опыт работы с LINQ и решетом Эратосфена.

person dbasnett    schedule 05.06.2010
comment
Я ожидаю, что мой будет медленнее, потому что я угадываю тип вызова Cast‹string›(). Что касается linq, то при правильном использовании он может быть очень быстрым, но вы должны быть осторожны. Каждый раз, когда вы обнаружите, что вызываете .ToList() или .ToArray(), вы убиваете производительность. В этом случае необходимо избежать состояния потенциальной гонки. - person Joel Coehoorn; 05.06.2010
comment
@Joel Coehoorn - а как насчет того, чтобы результаты были другими? Это только я, но я никогда не видел случая, чтобы LINQ был быстрее для такого рода вещей. - person dbasnett; 05.06.2010
comment
Ты прав. Код будет использоваться два раза в месяц с набором данных менее 10 000 записей. Тесты, которые я провел с примерно 3000 записями, заняли около 3 секунд, что, безусловно, превосходит старые методы, которые они использовали (ручное сравнение). Я не ожидал от вас такой большой помощи, ребята, обратная связь была не чем иным, как удивительным. Большое тебе спасибо. - person Rafael Sampaio; 06.06.2010