从struct数组中删除重复项

时间:2011-08-14 19:18:37

标签: c# .net c#-4.0

我无法想象从struct

数组中删除重复条目

我有这个结构:

public struct stAppInfo
{
    public string sTitle;
    public string sRelativePath;
    public string sCmdLine;
    public bool bFindInstalled;
    public string sFindTitle;
    public string sFindVersion;
    public bool bChecked;
}

由于 Jon Skeet

,我已将stAppInfo结构更改为类here

代码如下:(简短版)

stAppInfo[] appInfo = new stAppInfo[listView1.Items.Count];

int i = 0;
foreach (ListViewItem item in listView1.Items)
{
    appInfo[i].sTitle = item.Text;
    appInfo[i].sRelativePath = item.SubItems[1].Text;
    appInfo[i].sCmdLine = item.SubItems[2].Text;
    appInfo[i].bFindInstalled = (item.SubItems[3].Text.Equals("Sí")) ? true : false;
    appInfo[i].sFindTitle = item.SubItems[4].Text;
    appInfo[i].sFindVersion = item.SubItems[5].Text;
    appInfo[i].bChecked = (item.SubItems[6].Text.Equals("Sí")) ? true : false;
    i++;
}

我需要 sTitle sRelativePath 成员中 appInfo 数组唯一,其他成员可以重复

修改

感谢大家的答案,但这个应用程序是“可移植的”我的意思是我只需要.exe文件,我不想添加其他文件,如引用* .dll所以请不要外部引用此应用程序的目的是在pendrive中使用

所有数据都来自* .ini文件,我所做的是:(伪代码)

ReadFile()
FillDataFromFileInAppInfoArray()
DeleteDuplicates()
FillListViewControl()

当我想将这些数据保存到文件中时,我有以下选项:

  1. 使用ListView数据
  2. 使用appInfo数组(这样更快?)
  3. 还有其他吗?
  4. EDIT2:

    非常感谢:Jon Skeet,Michael Hays感谢您的时间!

4 个答案:

答案 0 :(得分:9)

首先,不要使用可变结构。他们在各方面都是个坏主意。

其次,不要使用公共字段。字段应该是实现细节 - 使用属性。

第三,我并不清楚这应该是一个结构。它看起来相当大,而不是特别“单一价值”。

第四,请遵循.NET naming conventions,以便您的代码适合用.NET编写的所有其余代码。

第五,你不能从数组中删除项目,因为数组是用固定大小创建的......但你可以创建一个只包含唯一元素的新数组。

LINQ to Objects将允许您使用Albin所示的GroupBy执行该操作,但稍微整洁(在我看来)的方法是使用DistinctBy中的MoreLINQ

var unique = appInfo.DistinctBy(x => new { x.sTitle, x.sRelativePath })
                    .ToArray();

这通常比GroupBy更有效,在我看来也更优雅。

我个人通常更喜欢使用List<T>而不是数组,但上面会为你创建一个数组。

请注意,使用此代码仍然可以有两个具有相同标题的项目,并且仍然可以有两个具有相同相对路径的项目 - 不能有两个具有相同相对路径的项目和< / em>标题。如果有重复的项目,DistinctBy将始终从输入序列中生成 first 此类项目。

编辑:只是为了满足迈克尔,你实际上并不需要创建一个数组来开始,或者如果你不需要它就创建一个数组

var query = listView1.Items
                     .Cast<ListViewItem>()
                     .Select(item => new stAppInfo
                             {
                                 sTitle = item.Text,
                                 sRelativePath = item.SubItems[1].Text,
                                 bFindInstalled = item.SubItems[3].Text == "Sí",
                                 sFindTitle = item.SubItems[4].Text,
                                 sFindVersion = item.SubItems[5].Text,
                                 bChecked = item.SubItems[6].Text == "Sí"
                             })
                     .DistinctBy(x => new { x.sTitle, x.sRelativePath });

这将为您提供一个懒惰的流媒体IEnumerable<appInfo>。请注意,如果您多次迭代它,它迭代listView1.Items相同的次数,每次执行相同的唯一性比较。

我比Michael更喜欢这种方法,因为它使得“distinct by”列在语义上非常清晰,并且删除了用于从ListViewItem中提取这些列的代码的重复。是的,它涉及构建更多的对象,但我更喜欢清晰度而不是效率,直到基准测试证明实际需要更高效的代码。

答案 1 :(得分:2)

使用LINQ2Objects,按照应该唯一的内容进行分组,然后选择每个组中的第一项。

var noDupes = appInfo.GroupBy(
    x => new { x.sTitle, x.sRelativePath })
    .Select(g => g.First()).ToArray();

答案 2 :(得分:2)

你需要的是一套。它确保输入的项目是唯一的(基于您将设置的一些限定符)。以下是它的完成方式:

  • 首先,将您的结构更改为类。真的没有那种解决方法。

  • 其次,提供IEqualityComparer<stAppInfo>的实现。这可能是一件麻烦事,但它会使你的工作成功(我们马上会看到):

    public class AppInfoComparer : IEqualityComparer<stAppInfo>
    {
        public bool Equals(stAppInfo x, stAppInfo y) {
            if (ReferenceEquals(x, y)) return true;
            if (x == null || y == null) return false;
            return Equals(x.sTitle, y.sTitle) && Equals(x.sRelativePath,
               y.sRelativePath);
        }
    
        // this part is a pain, but this one is already written 
        // specifically for your question.
        public int GetHashCode(stAppInfo obj) {
            unchecked {
                return ((obj.sTitle != null 
                    ? obj.sTitle.GetHashCode() : 0) * 397)
                    ^ (obj.sRelativePath != null 
                    ? obj.sRelativePath.GetHashCode() : 0);
            }
        }
    }
    
  • 然后,当您需要制作套装时,请执行以下操作:

    var appInfoSet = new HashSet<stAppInfo>(new AppInfoComparer());
    foreach (ListViewItem item in listView1.Items)
    {
        var newItem = new stAppInfo { 
            sTitle = item.Text,
            sRelativePath = item.SubItems[1].Text,
            sCmdLine = item.SubItems[2].Text,
            bFindInstalled = (item.SubItems[3].Text.Equals("Sí")) ? true : false,
            sFindTitle = item.SubItems[4].Text,
            sFindVersion = item.SubItems[5].Text,
            bChecked = (item.SubItems[6].Text.Equals("Sí")) ? true : false};
        appInfoSet.Add(newItem);
    }
    
  • appInfoSet现在包含一组stAppInfo个具有唯一标题/路径组合的对象,根据您的要求。如果您必须有一个数组,请执行以下操作:

    stAppInfo[] appInfo = appInfoSet.ToArray();
    

注意:我选择了这个实现,因为它看起来像你已经在做的事情。它有一个易于阅读的for循环(虽然我不需要计数器变量)。它不涉及LINQ(如果你不熟悉它可能很麻烦)。它不需要.NET框架为您提供的外部库。最后,它提供了一个就像你问过的数组。至于从INI文件中读取文件,希望您看到唯一可以改变的是foreach循环。

<强>更新

哈希码可能很痛苦。您可能一直想知道为什么需要计算它们。毕竟,你不能只是比较每次插入后的标题和相对路径的值吗?当然可以肯定,这就是另一个名为SortedSet的集合。 SortedSet使您以与我上面IComparer相同的方式实施IEqualityComparer

因此,在这种情况下,AppInfoComparer将如下所示:

private class AppInfoComparer : IComparer<stAppInfo>
{
   // return -1 if x < y, 1 if x > y, or 0 if they are equal
   public int Compare(stAppInfo x, stAppInfo y)
   {
      var comparison = x.sTitle.CompareTo(y.sTitle);
      if (comparison != 0) return comparison;
      return x.sRelativePath.CompareTo(y.sRelativePath);
   }
}

然后,您需要做的唯一其他更改是使用SortedSet而不是HashSet:

var appInfoSet = new SortedSet<stAppInfo>(new AppInfoComparer());

事实上,这很容易,你可能想知道是什么给出的?大多数人选择HashSet而不是SortedSet的原因是效果。但是你应该平衡你实际关心的程度,因为你将维护这些代码。我个人使用一个名为Resharper的工具,它可用于Visual Studio,它为我计算这些哈希函数,因为我认为计算它们也很痛苦。

(我将谈论这两种方法的复杂性,但如果你已经知道它,或者没有兴趣,可以随意跳过它。)

SortedSet的复杂度为O(log n),也就是说,每次输入新项目时,都会有效地进入集合的中间点并进行比较。如果它没有找到您的条目,它将进入其最后一次猜测与该猜测左侧或右侧的组之间的中间点,快速缩小您的元素隐藏的位置。对于一百万个条目,这需要大约20次尝试。一点也不差。但是,如果你选择了一个好的散列函数,那么HashSet平均可以在一次比较中做同样的工作,即O(1)。在您认为20不是真的与1相比之前(在所有计算机都非常快)之后,请记住您必须插入这些百万项,所以在HashSet进行时一百万次尝试构建该设置,SortedSet进行了数百万次尝试。但是有一个价格 - HashSet如果你选择一个糟糕的散列函数就会崩溃(非常糟糕)。如果许多项目的数字是唯一的,那么它们将在HashSet中发生碰撞,然后必须反复尝试。如果许多物品与完全相同的号码发生碰撞,那么它们将会回溯其他步骤,您将等待很长时间。第一百万次尝试将花费一百万次尝试 - HashSet已经转变为O(n ^ 2)。那些大O符号(实际上是O(1),O(log n)和O(n ^ 2))的重要之处在于括号中的数字随着n的增加而增长的速度有多快。增长缓慢或没有增长是最好的。快速增长有时是不可避免的。对于十几个甚至一百个项目,差异可能是微不足道的 - 但是如果你养成了编程有效功能的习惯,就像替代方案一样容易,那么值得调整自己这样做,因为问题是最便宜的,最接近于你在哪里创造了这个问题。

答案 3 :(得分:0)

!!! 结构数组(值类型)+排序或任何类型的搜索==&gt;很多拆箱操作。

  1. 我建议坚持使用Jon和Henk的建议,所以将它作为一个类使用泛型List<T>
  2. 使用LINQ GroupBy或DistinctBy,对我来说使用内置的GroupBy非常简单,但看看其他流行的库也很有趣,也许它会给你一些见解。
  3. 顺便说一句,另外看一下LambdaComparer,每当你需要这样的排序/搜索等时,它会让你的生活更轻松......