Удаление повторяющихся элементов из списка с несколькими столбцами

Ответ на вопрос

Спасибо Дэн! Ваш код сработал отлично, и сегодня вы спасли мне жизнь! Много интернетов вам добрый сэр.

Исходный

Сообщество великодушно подсказало мне использовать LINQ для поиска дубликатов в моих списках в последний раз. Однако сейчас я нахожусь в затруднительном положении, потому что мне нужно найти и удалить дубликаты из представления списка с несколькими столбцами. Я пытался использовать LINQ, но он говорит, что объект списка не доступен для запроса. Есть ли способ найти и удалить дубликаты, используя только один столбец списка?

Спасибо

ОБНОВЛЕНИЕ

Private Shared Sub RemoveDuplicateListViewItems(ByVal listView As ListView)
    Dim duplicates = listView.Items.Cast(Of ListViewItem)() _
    .GroupBy(Function(item) item.Text)
    .Where(Function(g) g.CountAtLeast(2))
    .SelectMany(Function(g) g)

    For Each duplicate As ListViewItem In duplicates
        listView.Items.RemoveByKey(duplicate.Name)
    Next
End Sub

Это то, что у меня есть до сих пор благодаря Дэну. Все еще получаю ошибки в строке дубликатов Dim.

ОБНОВЛЕНИЕ 2 Вот код модуля и функции внутри формы:

Imports System.Runtime.CompilerServices

Module CountAtLeastExtension
    <Extension()> _
    Public Function CountAtLeast(Of T)(ByVal source As IEnumerable(Of T), ByVal minimumCount As Integer) As Boolean
        Dim count = 0
        For Each item In source
            count += 1
            If count >= minimumCount Then
                Return True
            End If
        Next

    Return False
End Function
End Module

    Private Shared Sub RemoveDuplicateListViewItems(ByVal listView As ListView)
    Dim duplicates = listView.Items.Cast(Of ListViewItem)() _
        .GroupBy(Function(item) item.Text) _
        .Where(Function(g) g.CountAtLeast(2)) _
        .SelectMany(Function(g) g)

    For Each duplicate As ListViewItem In duplicates
        listView.Items.RemoveByKey(duplicate.Name)
    Next
End Sub

Теперь код работает нормально, когда я его вызываю. Но он не удаляет дубликаты:

Пример дубликата

Может быть, на этом снимке экрана вы можете увидеть, к чему я здесь стремлюсь. Большое спасибо за то, что вы так терпеливы со мной!


person Rafael Sampaio    schedule 30.08.2010    source источник
comment
Был ли список с несколькими столбцами заполнен объектами? Если это так, вы можете сначала добавить его в список. Также вы хотите удалить дубликаты в столбце или в строке (каждый столбец в строке совпадает с другой строкой)   -  person Gage    schedule 30.08.2010
comment
Он заполняется из файла MDB и XLS (только текст). Моя цель состоит в том, что если я найду повторяющееся значение в столбце 0, эти две строки будут удалены, оставив уникальные записи нетронутыми.   -  person Rafael Sampaio    schedule 30.08.2010


Ответы (1)


Что ж, вам понадобится какой-нибудь метод для определения того, являются ли два объекта ListViewItem дубликатами.

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

Допустим, вы хотите считать два элемента одинаковыми, если текст в первом столбце одинаковый (например). Затем вы можете написать быструю реализацию IEqualityComparer<ListViewItem>, например:

class ListViewItemComparer : IEqualityComparer<ListViewItem>
{
    public bool Equals(ListViewItem x, ListViewItem y)
    {
        return x.Text == y.Text;
    }

    public int GetHashCode(ListViewItem obj)
    {
        return obj.Text.GetHashCode();
    }
}

Затем вы можете удалить дубликаты следующим образом:

static void RemoveDuplicateListViewItems(ListView listView)
{
    var uniqueItems = new HashSet<ListViewItem>(new ListViewItemComparer());

    for (int i = listView.Count - 1; i >= 0; --i)
    {
        // An item will only be added to the HashSet<ListViewItem> if an equivalent
        // item is not already contained within. So a return value of false indicates
        // a duplicate.
        if (!uniqueItems.Add(listView.Items[i]))
        {
            listView.Items.RemoveAt(i);
        }
    }
}

ОБНОВЛЕНИЕ: приведенный выше код удаляет дубликаты любых элементов, которые появляются в ListView более одного раза; то есть он оставляет по одному экземпляру каждого. Если на самом деле вы хотите удалить все экземпляры любых элементов, которые появляются более одного раза, подход немного отличается.

Вот один из способов сделать это. Сначала определите следующий метод расширения:

public static bool CountAtLeast<T>(this IEnumerable<T> source, int minimumCount)
{
    int count = 0;
    foreach (T item in source)
    {
        if ((++count) >= minimumCount)
        {
            return true;
        }
    }

    return false;
}

Затем найдите дубликаты следующим образом:

static void RemoveDuplicateListViewItems(ListView listView)
{
    var duplicates = listView.Items.Cast<ListViewItem>()
        .GroupBy(item => item.Text)
        .Where(g => g.CountAtLeast(2))
        .SelectMany(g => g);

    foreach (ListViewItem duplicate in duplicates)
    {
        listView.Items.RemoveByKey(duplicate.Name);
    }
}

ОБНОВЛЕНИЕ 2. Похоже, вы уже смогли преобразовать большую часть вышеперечисленного в VB.NET. Строка, которая вызывает у вас затруднения, может быть записана следующим образом:

' Make sure you have Option Infer On. '
Dim duplicates = listView.Items.Cast(Of ListViewItem)() _
    .GroupBy(Function(item) item.Text) _
    .Where(Function(g) g.CountAtLeast(2)) _
    .SelectMany(Function(g) g)

Кроме того, если у вас возникли проблемы с использованием метода CountAtLeast описанным выше способом, вам необходимо использовать атрибут ExtensionAttribute для написания методов расширения в VB.NET:

Module CountAtLeastExtension

    <Extension()> _
    Public Function CountAtLeast(Of T)(ByVal source As IEnumerable(Of T), ByVal minimumCount As Integer) As Boolean
        Dim count  = 0
        For Each item in source
            count += 1
            If count >= minimumCount Then
                Return True
            End If
        Next

        Return False
    End Function

End Module
person Dan Tao    schedule 30.08.2010
comment
Я изо всех сил пытался преобразовать его в VB.NET, но я не могу правильно перенести эту строку: var дубликаты = listView.Items.Cast‹ListViewItem›() .GroupBy(item =› item.Text) .Where(g =› g.CountAtLeast(2)) .SelectMany(g => g); Спасибо за вашу помощь! Я бы поместил здесь остальную часть переведенного кода, но ограничение на количество символов не позволяет этого. - person Rafael Sampaio; 30.08.2010
comment
@RedHaze: Извините, пропустил тег VB.NET! Я обновлю ответ. - person Dan Tao; 30.08.2010
comment
Я должен быть тем, кто благодарит вас здесь! У меня все еще есть проблемы с этой строкой (у меня была опция вывода), но я получаю сообщения об ошибках: '.' или же '!' может появляться только внутри оператора 'With'. Я обновлю верхнюю часть тем, что у меня есть в настоящее время, чтобы вы знали, с чем я работаю. - person Rafael Sampaio; 30.08.2010
comment
@RedHaze: Ну, я полностью пропустил некоторые необходимые _ символы; это было проблемой? - person Dan Tao; 31.08.2010
comment
Я попытался добавить дополнительные символы _, а также разместить все это в одной строке, но без особого успеха. Когда я это делаю, я получаю другую ошибку: Ошибка разрешения перегрузки, потому что с этими аргументами нельзя вызвать «Где» - person Rafael Sampaio; 31.08.2010
comment
@RedGaze: Это определенно работает для меня. Хотите обновить свой вопрос, указав точный код, вызывающий ошибку? - person Dan Tao; 31.08.2010
comment
Я добавил ссылку на изображение, чтобы вы могли видеть, где я получаю ошибки во время компиляции. Спасибо за ваше терпение! - person Rafael Sampaio; 31.08.2010
comment
@RedHaze: изображение, на которое вы ссылаетесь, показывает ошибки, возникающие из-за пропуска концов строк _! Попробуйте добавить их обратно, а затем включите ссылку на другую ошибку, которую, как вы сказали, вы получаете (ошибка разрешения перегрузки...). - person Dan Tao; 31.08.2010
comment
@RedHaze: Вы определили метод CountAtLeast так, как я сделал в своем ответе, в модуле с атрибутом Extension? Кажется, компилятор VB.NET на вашем скриншоте не знает об этом методе. В качестве альтернативы вы можете сделать это простой общей функцией и вызвать ее так: Function(g) CountAtLeast(g, 2) - person Dan Tao; 31.08.2010
comment
Это было именно так. Я получил код для компиляции и вызвал функцию внутри моего оператора, используя: RemoveDuplicateListViewItems(ListView1), но ничего не произошло. Должно быть, я делаю что-то действительно глупое где-то в этом коде. Я обновил свой пост там, чтобы показать изображение проблемы. Большое спасибо! - person Rafael Sampaio; 31.08.2010
comment
@RedHaze: я думаю, что могу догадаться, в чем проблема. Вашим объектам ListViewItem не назначено свойство Name? Ничего страшного: просто используйте Remove(duplicate) или RemoveAt(duplicate.Index) вместо RemoveByKey(duplicate.Name). - person Dan Tao; 31.08.2010