我无法想象从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()
当我想将这些数据保存到文件中时,我有以下选项:
EDIT2:
非常感谢:Jon Skeet,Michael Hays感谢您的时间!
答案 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;很多拆箱操作。
List<T>
。顺便说一句,另外看一下LambdaComparer,每当你需要这样的排序/搜索等时,它会让你的生活更轻松......