0

当第一个、第二个或两个属性相等(在列表中出现多次)时,尝试从列表中删除重复项。使用 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);
}

问题: 是否有更有效和/或更短的方法来实现这一目标?

4

3 回答 3

5

您可以使用.DistinctLINQ 方法来执行此操作。您必须定义一个自定义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()必须为被认为相等的两个对象返回相同的值。由于我们的相等概念是不可传递的,因此满足此要求的唯一方法是返回一个常量值。这是允许的,但违背了哈希码的目标,这是一种加速相等性检查的方法:如果哈希码不同,则对象必须不同,而无需潜在地更昂贵的“深度”比较。

因此,此代码有效,但未达到尽可能高的性能。

于 2018-01-18T18:04:42.400 回答
1

这是另一种使用标准 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();

更好的是在一个单独的类中创建一个扩展方法并将唯一的属性比较逻辑推到那里。

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();
于 2018-01-18T22:02:13.370 回答
0

您可以通过首先多次获取第一个和第二个属性存在的所有值来执行两次传递,然后对其进行过滤。每个属性都需要 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();
于 2018-01-18T19:35:13.043 回答