有没有更好的方法来删除列表中多个属性的重复值?
Is there a better way to remove duplicate values on multiple properties in a List?
尝试从列表中删除重复项,其中重复项是第一个、第二个或两个属性相等(在列表中出现多次)。使用 MoreLINQ,以下代码有效:
var list = new List<LinqTest> // LinqTest: object containing 2 strings
{
// are ok
new LinqTest { Str1 = "a1", Str2 = "b1"},
new LinqTest { Str1 = "a2", Str2 = "b2"},
new LinqTest { Str1 = "a3", Str2 = "b3"},
new LinqTest { Str1 = "a5", Str2 = "b5"},
new LinqTest { Str1 = "a6", Str2 = "b6"},
new LinqTest { Str1 = "x1", Str2 = "y1"},
new LinqTest { Str1 = "y1", Str2 = "x1"},
// must be removed
new LinqTest { Str1 = "d1", Str2 = "b4"},
new LinqTest { Str1 = "d1", Str2 = "d2"},
new LinqTest { Str1 = "d1", Str2 = "d2"},
new LinqTest { Str1 = "a4", Str2 = "d2"},
new LinqTest { Str1 = "d3", Str2 = "b7"},
new LinqTest { Str1 = "d3", Str2 = "b8"},
new LinqTest { Str1 = "d3", Str2 = "b8"},
};
var duplicatesStr1 = list
.GroupBy(x => x.Str1)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToList();
var duplicatesStr2 = list
.GroupBy(x => x.Str2)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToList(); ;
var res = list
.ExceptBy(duplicatesStr1, x => x.Str1)
.ExceptBy(duplicatesStr2, x => x.Str2);
var rem = duplicatesStr1
.Union(duplicatesStr2)
.DistinctBy(x => new { x.Str1, x.Str2})
.ToList();
Console.WriteLine("----------");
foreach (var linqTest in res)
{
Console.WriteLine("keep> " + linqTest.Str1 + "-" + linqTest.Str2);
}
Console.WriteLine("----------");
foreach (var linqTest in rem)
{
Console.WriteLine("remove> " + linqTest.Str1 + "-" + linqTest.Str2);
}
问题:
是否有更高效 and/or 更短的方法来实现此目的?
您可以使用 .Distinct
LINQ method to do this. You'll have to define a custom IEqualityComparer
来决定何时将两个元素视为不同的。
public class MyComparer : IEqualityComparer<LinqTest>
{
public bool Equals(LinqTest x, LinqTest y)
{
return x.Str1 == y.Str1 || x.Str2 == y.Str2;
}
public int GetHashCode(LinqTest obj)
{
return 0;
}
}
那你可以这样写:
List<LinqTest> noDuplicates = originalList.Distinct(new MyComparer()).ToList();
棘手的部分是正确实施 IEqualityComparer
(我第一次没有这样做!)。 GetHashCode()
必须 return 两个被认为相等的对象具有相同的值。由于我们的平等概念是不可传递的,因此满足此要求的唯一方法是 return 一个常量值。这是允许的,但违背了哈希码的目标,哈希码是一种加速相等性检查的方法:如果哈希码不同,则对象必须不同,而不需要潜在的更昂贵的 "deep" 比较。
因此,此代码有效,但未达到可能的最高性能。
您可以通过首先多次获取第一个和第二个属性的所有值,然后对其进行过滤来执行两次传递。每个 属性 需要 2 个哈希集。第一个是跟踪某个值是否至少出现过一次。如果它至少被看过一次,它就会被添加到第二个哈希集中。因此,每个 属性 的哈希集只包含重复的值。然后过滤掉这些哈希集中的任何项目。
HashSet<string> hash1Once = new HashSet<string>();
HashSet<string> hash1More = new HashSet<string>();
HashSet<string> hash2Once = new HashSet<string>();
HashSet<string> hash2More = new HashSet<string>();
foreach(var item in list){
if(!hash1Once.Add(item.Str1))
hash1More.Add(item.Str1);
if(!hash2Once.Add(item.Str2))
hash2More.Add(item.Str2);
}
var unique = list.Where(x => !hash1More.Contains(x.Str1) && !hash2More.Contains(x.Str2))
.ToList();
这是另一种使用标准 LINQ ToDictionary() 方法删除基于 属性 比较的重复项的方法。创建一个字典来累积整个列表中的匹配项计数,并过滤结果字典以查找具有单个匹配项的项。
list = list.ToDictionary(
test => test,
test => list.Count(item => item.Str1 == test.Str1 || item.Str2 == test.Str2)
).Where(pair => pair.Value == 1)
.Select(pair => pair.Key)
.ToList();
更好的方法是在单独的 class 中创建一个扩展方法,并将独特的 属性 比较逻辑推送到那里。
internal static class EnumerableExtensions
{
public static IEnumerable<T> Unique<T>(
this IEnumerable<T> sequence,
Func<T, T, bool> match)
{
var list = sequence.ToList();
return list
.ToDictionary(arg => arg, arg => list.Count(item => match(item, arg)))
.Where(pair => pair.Value == 1)
.Select(pair => pair.Key);
}
}
作为扩展方法公开会导致方法签名:
list = list.Unique((a, b) => a.Str1 == b.Str1 || a.Str2 == b.Str2).ToList();
尝试从列表中删除重复项,其中重复项是第一个、第二个或两个属性相等(在列表中出现多次)。使用 MoreLINQ,以下代码有效:
var list = new List<LinqTest> // LinqTest: object containing 2 strings
{
// are ok
new LinqTest { Str1 = "a1", Str2 = "b1"},
new LinqTest { Str1 = "a2", Str2 = "b2"},
new LinqTest { Str1 = "a3", Str2 = "b3"},
new LinqTest { Str1 = "a5", Str2 = "b5"},
new LinqTest { Str1 = "a6", Str2 = "b6"},
new LinqTest { Str1 = "x1", Str2 = "y1"},
new LinqTest { Str1 = "y1", Str2 = "x1"},
// must be removed
new LinqTest { Str1 = "d1", Str2 = "b4"},
new LinqTest { Str1 = "d1", Str2 = "d2"},
new LinqTest { Str1 = "d1", Str2 = "d2"},
new LinqTest { Str1 = "a4", Str2 = "d2"},
new LinqTest { Str1 = "d3", Str2 = "b7"},
new LinqTest { Str1 = "d3", Str2 = "b8"},
new LinqTest { Str1 = "d3", Str2 = "b8"},
};
var duplicatesStr1 = list
.GroupBy(x => x.Str1)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToList();
var duplicatesStr2 = list
.GroupBy(x => x.Str2)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToList(); ;
var res = list
.ExceptBy(duplicatesStr1, x => x.Str1)
.ExceptBy(duplicatesStr2, x => x.Str2);
var rem = duplicatesStr1
.Union(duplicatesStr2)
.DistinctBy(x => new { x.Str1, x.Str2})
.ToList();
Console.WriteLine("----------");
foreach (var linqTest in res)
{
Console.WriteLine("keep> " + linqTest.Str1 + "-" + linqTest.Str2);
}
Console.WriteLine("----------");
foreach (var linqTest in rem)
{
Console.WriteLine("remove> " + linqTest.Str1 + "-" + linqTest.Str2);
}
问题: 是否有更高效 and/or 更短的方法来实现此目的?
您可以使用 .Distinct
LINQ method to do this. You'll have to define a custom IEqualityComparer
来决定何时将两个元素视为不同的。
public class MyComparer : IEqualityComparer<LinqTest>
{
public bool Equals(LinqTest x, LinqTest y)
{
return x.Str1 == y.Str1 || x.Str2 == y.Str2;
}
public int GetHashCode(LinqTest obj)
{
return 0;
}
}
那你可以这样写:
List<LinqTest> noDuplicates = originalList.Distinct(new MyComparer()).ToList();
棘手的部分是正确实施 IEqualityComparer
(我第一次没有这样做!)。 GetHashCode()
必须 return 两个被认为相等的对象具有相同的值。由于我们的平等概念是不可传递的,因此满足此要求的唯一方法是 return 一个常量值。这是允许的,但违背了哈希码的目标,哈希码是一种加速相等性检查的方法:如果哈希码不同,则对象必须不同,而不需要潜在的更昂贵的 "deep" 比较。
因此,此代码有效,但未达到可能的最高性能。
您可以通过首先多次获取第一个和第二个属性的所有值,然后对其进行过滤来执行两次传递。每个 属性 需要 2 个哈希集。第一个是跟踪某个值是否至少出现过一次。如果它至少被看过一次,它就会被添加到第二个哈希集中。因此,每个 属性 的哈希集只包含重复的值。然后过滤掉这些哈希集中的任何项目。
HashSet<string> hash1Once = new HashSet<string>();
HashSet<string> hash1More = new HashSet<string>();
HashSet<string> hash2Once = new HashSet<string>();
HashSet<string> hash2More = new HashSet<string>();
foreach(var item in list){
if(!hash1Once.Add(item.Str1))
hash1More.Add(item.Str1);
if(!hash2Once.Add(item.Str2))
hash2More.Add(item.Str2);
}
var unique = list.Where(x => !hash1More.Contains(x.Str1) && !hash2More.Contains(x.Str2))
.ToList();
这是另一种使用标准 LINQ ToDictionary() 方法删除基于 属性 比较的重复项的方法。创建一个字典来累积整个列表中的匹配项计数,并过滤结果字典以查找具有单个匹配项的项。
list = list.ToDictionary(
test => test,
test => list.Count(item => item.Str1 == test.Str1 || item.Str2 == test.Str2)
).Where(pair => pair.Value == 1)
.Select(pair => pair.Key)
.ToList();
更好的方法是在单独的 class 中创建一个扩展方法,并将独特的 属性 比较逻辑推送到那里。
internal static class EnumerableExtensions
{
public static IEnumerable<T> Unique<T>(
this IEnumerable<T> sequence,
Func<T, T, bool> match)
{
var list = sequence.ToList();
return list
.ToDictionary(arg => arg, arg => list.Count(item => match(item, arg)))
.Where(pair => pair.Value == 1)
.Select(pair => pair.Key);
}
}
作为扩展方法公开会导致方法签名:
list = list.Unique((a, b) => a.Str1 == b.Str1 || a.Str2 == b.Str2).ToList();