【问题标题】:Listview, remove duplicates using LINQ and based in subitem comparingListview,使用 LINQ 删除重复项并基于子项比较
【发布时间】:2014-02-18 01:54:07
【问题描述】:

我想根据子项索引从 ListViewItemCollection 中删除重复项。

使用LINQ 的方法很重要。

这是我没有运气的尝试:

Private Function RemoveListViewDuplicates(ByVal Items As ListView.ListViewItemCollection,
                                          ByVal CompareColumn As Integer) As ListView.ListViewItemCollection

    Dim Deduplicated =
        (From Item As ListViewItem In Items
         Group By Item.SubItems(CompareColumn).Text Into Distinct()) '.ToArray
    ' At this point the good thing is that the '.Distinct' grouped member of the 
    ' resulting 'Deduplicated' object contains only the non-duplicated ListviewItems
    ' but, I can't find the way to return that single member '.Distinct' as a 'ListView.ListViewItemCollection'
    ' instead of returning both '.Text' and '.Distinct' members.
    '
    ' I just want to return the 'Items' object that I pass to the function but without duplicates,
    ' and in the same return-type of the object that I've passed to this function 
    ' (ListView.ListViewItemCollection) as you can understand.

    Return Deduplicated

End Function

然后我应该可以这样做:

    Dim items As ListView.ListViewItemCollection =
        RemoveListViewDuplicates(ListView1.Items, 0)

    For Each Item As ListViewItem In items
        MsgBox(Item.Text)
    Next Item

更新

我正在尝试回答here 的解决方案,但稍作修改以保留集合中的 1 个重复项,我遇到的问题是这直接在 ListView 控件上工作,这不是我想要的,我想要处理存储列表视图控件的当前项但在删除重复项时不直接影响控件的对象...

这是方法:

Private Function RemoveListViewDuplicates(ByVal Items As ListView.ListViewItemCollection,
                                          ByVal SubitemCompare As Integer) As ListView.ListViewItemCollection

    Dim Duplicates As ListViewItem() =
        Items.Cast(Of ListViewItem)().
        GroupBy(Function(Item As ListViewItem) Item.SubItems(SubitemCompare).Text).
        Where(Function(g As IGrouping(Of String, ListViewItem)) g.Count <> 1).
        SelectMany(Function(g As IGrouping(Of String, ListViewItem)) g).
        Skip(1).
        ToArray()

    ' This removes the items from the listview control, directlly
    For Each Item As ListViewItem In Duplicates
        Items.Remove(Item)
    Next Item

    Return Items

End Function

所需用途:

    Dim DuplicatedItems As ListView.ListViewItemCollection =
        New ListView.ListViewItemCollection(ListView1)

    Dim DeDuplicatedItems As ListView.ListViewItemCollection =
        RemoveListViewDuplicates(DuplicatedItems, 0)

    ' I add the Items without duplicates on another Listview, 
    ' preserving the duplicates on the original Listview1 control.
    ListView2.Items.AddRange(DeDuplicatedItems)

【问题讨论】:

  • 你见过吗:stackoverflow.com/q/3603036/1070452 我要提到一个在那里使用的比较器。另外,我很好奇为什么使用 LINQ 很“重要”。
  • @Plutonix 是的,我在创建问题之前已经看过它,但老实说,我没有阅读答案末尾有趣的部分。我正在尝试使用该解决方案,但给了我一个小问题,也许很容易为你解决,如果你能阅读我更新的问题,请。 PS:我喜欢 LINQ 语法,因为它简化了分句,我知道 For 循环更有效,但我想以多种方式替换 For 的用法。

标签: .net vb.net linq visual-studio listview


【解决方案1】:

我认为您的方法存在 2 个基本问题。首先,ListViewItems 是对象。当它们从 ListViewItemCollection 中移除时,有 2 个属性会发生变化:Index(变为 0)和 ListView(所有者)属性变为 Nothing。因此,当您在 Collection2 中删除 ListViewItem 时,它也会从主/原始集合中删除,因为它们是相同的 LVI 对象

链接到(并在上面复制)的另一个 SO 答案不是一个合适的起点,因为一个确实想要从您不想从源中删除。 解决方案:如果您想要 2 个包含不同 LVI 项目的集合(1==w/dupes;2==no dupes),您必须克隆项目 - 或者将第一个集合中的所有项目从 DupesCollection 开始或克隆 Dupes。除了在循环中(即通过 LINQ)之外,我不知道你会如何做到这一点。

如果您在开始时克隆整个集合,您应该能够按照您的意愿继续执行 LINQ 路径,只需确保您正在处理克隆的集合。不过,这似乎昂贵,只是为了能够使用 LINQ(尾巴在摇狗)。

其次,ListViewItemCollection 需要 LV 所有者。当您创建其他集合时,您将其分配给特定的 LV:

Dim DuplicatedItems As ListView.ListViewItemCollection =
    New ListView.ListViewItemCollection(ListView1)

新集合不能重新分配给不同的 LV(看起来你想在不同的 LV 中显示 Dupes)。您不能更改所有者,也不能直接替换Items。您应该能够在开始时将其分配给所需的目标 LV,但 必须克隆这些项目。否则,将 Dupe Items 集合移动到 LV2 的唯一方法是循环。

我不确定将 2 个 LVI 集合分配给一个 LV 会发生什么,但是当您真正需要的只是重复索引时,创建一个新集合似乎很昂贵:

   ' get a list of indices comparing the Text for SubItem(index)
    Public Function DuplicateItemIndices(index As Integer) As List(Of Integer)
        Dim lvDupes As New List(Of Integer)

        If Items.Count = 0 Then Return Nothing
        If Items(0).SubItems.Count < index Then Return Nothing

        Dim ndx As Integer

        For Each lvi As ListViewItem In Items
            ' already caught, keep going
            If lvDupes.Contains(lvi.Index) Then Continue For

            ndx = FindDupeItem(lvi, index)
            ' not sure the second condition can happen here
            If ndx > 0 AndAlso lvDupes.Contains(ndx) = False Then
                lvDupes.Add(ndx)
            End If
        Next

        Return lvDupes

    End Function

    ' returns an index for an item matching this one
    Private Function FindDupeItem(lvi As ListViewItem, 
                                  index As Integer) As Integer

        For Each lvx As ListViewItem In Items
            If lvx Is lvi Then Continue For

            If lvx.SubItems(index).Text = lvi.SubItems(index).Text Then
                Return lvx.Index
            End If
        Next
        ' signal for no match
        Return -1

    End Function

就我理解您的目的而言,请使用返回的 List(of DuplicateItemIndices)克隆主列表中的项目并存储在 DuplicatesLV 中。

【讨论】:

    【解决方案2】:

    试试下面的例子:

    Dim StringList As List(Of String) = ...
    StringList = StringList.Distinct().ToList()
    

    更新

    试试下面的。请记住,我习惯于编写 C#,所以我可能会留下一点 C#。

    Option Infer On
    Imports System.Linq
    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 RemoveListViewDuplicates(ByVal listView As ListView)
    
        Dim duplicates = listView.Items.Cast(Of ListViewItem.SubItems)() _
            .GroupBy(Function(item) item.Text) _
            .Where(Function(g) g.CountAtLeast(2)) _
            .SelectMany(Function(g) g)
    
        For Each duplicate As ListViewItem In duplicates
            listView.SubItems.RemoveByKey(duplicate.Name)
        Next
    
    End Sub
    
    Private Shared Sub RemoveDuplicateListViewItems(ByVal ListView As ListView.SubItems)
    
        Dim uniqueItems = New HashSet(Of ListViewItem)
    
        For Each item As String In ListView
    
            If !uniqueItems.Add(ListView.Items) Then
                ListView.SubItems.RemoveAt(item)
            End If
    
        Next
    End Sub
    

    【讨论】:

    • 感谢您的回答,但请不要错过我提到的一些重要事项:这些项目有子项目,我只想通过其中一个子项目进行比较,然后字符串列表对我没有帮助.
    • 我还需要简化一些事情,让函数进行所有需要的类型转换(如果需要),最后返回一个ListViewItemCollectionv,因为我在函数参数上传递了ListViewItemCollectionv我不想弄乱转换,例如返回 ListviewItem() 数组或其他不同的返回数据类型...正如我在这里提到的:我只想返回传递给函数但没有的对象重复,并且在我传递给此函数的对象的相同返回类型中(我的意思是“ListViewItemCollection”)。再次感谢。
    • 我更新了我的回复。同样,请记住,我习惯于编写 C#,所以有些事情可能会出错。但它至少应该让你知道需要发生什么。
    猜你喜欢
    • 2015-07-15
    • 1970-01-01
    • 2014-12-16
    • 2017-12-31
    • 1970-01-01
    • 1970-01-01
    • 2013-02-02
    • 2020-12-14
    • 2019-12-10
    相关资源
    最近更新 更多