从C#的列表中完全删除至少具有一个重复项的所有元素

时间:2018-08-28 14:50:11

标签: c# list filter

简介

我是一名比利时软件工程师,在一家生产折弯机的公司工作。我现在有一个有趣的问题,我想知道最好的解决方案,在我的工作环境中,性能确实很重要。我认为这对其他程序员也可能很有趣。

数据

  • 我有一个列表,其中包含一堆类型为“ CS3DLine”的对象。

    List <CS3DLine> ListParallelLines = new List<CS3DLine>();
    
  • 我还有一个自定义方法,该方法将其中两个对象用作参数,并返回一个布尔值,指示这两个对象是否相等。

    public static bool IsSameLineIn3D(CS3DLine povleft, CS3DLine povright)
    

想要

我想获得一个FilteredListParallelLines,其中等于完全的CS3DLines将从列表中过滤掉。

备注

  • 我在Internet上找到了使用Distinct方法和IEqualityComparer的示例(例如,在dotNetPerls的此页面上),但是在这些情况下,仅删除了重复项,而没有删除重复项的原始项。
  • 我知道我也可以尝试迭代解决此问题,但是如果列表中包含大量对象,恐怕会导致性能下降。

4 个答案:

答案 0 :(得分:2)

如果我正确理解,则以下是基于集合的方法,可能会满足您的要求。我不能保证性能。

如果列表的顺序不重要,则可以简化。

在没有CS3DLine定义的情况下,我为自己的Line类提供了一个示例。

和以往一样,在使用基于集合的方法时,最好使线类是不可变的。

void Main()
{
    List<Line> lines = new List<Line>();
    var comparer = LineEqualityComparer.Instance;
    var filtered = lines
        .Select((line, idx) => new { line, idx })
        .GroupBy(x => x.line, comparer)
        .Where(g => g.Count() == 1)
        .SelectMany(g => g)
        .OrderBy(x => x.idx)
        .Select(x => x.line);
}

class Line
{
    public int X1 { get; }
    public int Y1 { get; }
    public int X2 { get; }
    public int Y2 { get; }
}

class LineEqualityComparer : IEqualityComparer<Line>
{
    public static IEqualityComparer<Line> Instance { get; } = new LineEqualityComparer();
    public bool Equals(Line x, Line y)
    {
        //fill-in the blanks
    }

    public int GetHashCode(Line obj)
    {
        //fill-in the blanks
    }
}

在大型数据集上,通过在linq方法链中策略性地放置.AsParallel() 某处,可以 在查询中获得更好的性能。

答案 1 :(得分:0)

对于复杂的对象,您需要重写Equals和GetHashCode之后,您可以对其进行比较

http://www.loganfranken.com/blog/687/overriding-equals-in-c-part-1/

答案 2 :(得分:0)

第一步,您需要创建一个为IEqualityComparer类实现CS3DLines的类。

这可能看起来像这样:

public class CS3DComparer : IEqualityComparer {
    public bool Equals(CS3DLines a, CS3DLines b) {
        return IsSameLineIn3D(a, b);
    }
    public int GetHashCode(CS3DLines line){
        // You do not need to use all properties of line to calculate the 
        // hashCode. If performance is not good enough you can experiment by 
        // adding and removing properties from the hash code calculation.

        var hashCode = line.Property1?.GetHashCode() ?? 0;
        hashCode = (hashCode * 397) ^ (line.Property2?.GetHashCode() ?? 0);
        hashCode = (hashCode * 397) ^ (line.Property3?.GetHashCode() ?? 0);
        return hashCode;
    }
}

接下来要获取ListParallelLines集合中所有元素的未排序列表,可以调用以下代码:

var singles = ListParallelLines
    .GroupBy(line => line, new CS3DComparer())
    .Where(group => group.Count() == 1)
    .Select(group => group.Key)
    .ToList();

singles现在是ListParallelLines中没有重复的所有行的列表。

要通过并行化实现可能的加速,您可以尝试通过调用AsParallel()启动LINQ查询来尝试使用PLINQ。

var singles = ListParallelLines
    .AsParallel()
    .GroupBy(line => line, new CS3DComparer())
    .Where(group => group.Count() == 1)
    .Select(group => group.Key)
    .ToList();

答案 3 :(得分:-1)

由于您需要从列表中完全删除任何重复项,因此一种方法是先将您的集合分组,然后根据具有多个项目的任何组进行过滤。

这种过滤的性能始终受到限制,但是如果您让对象提前维护自己的哈希值进行分组,则在分组和运行相等性比较时可以节省时间,这将减少想要过滤时的负担并且散列将需要通过对给定实例的更改进行一致地更新。如果您的硬件受到限制,则考虑的因素将有所不同,因此您不希望将所有项目的哈希存储在内存中,或者您担心的是速度。存储散列而不计算散列并不理想,因为代码中可能存在的移动部分可能会无意间不会触发散列更新,但是如果性能是一个很大的因素,则如果仔细实施可能会有所帮助。

var results = ListParallelLines.GroupBy(x => x.EqualityHash).Where(x => x.Count() == 1);

这将为哈希提供一个列表,该列表将向您返回没有重复项的列表。

有一个默认的GetHashCode()实现,但是它有很高的冲突几率,而且我在过去曾遇到过一个问题,由于该问题而引起了极大的头痛,因此请避免使用它。

https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode?redirectedfrom=MSDN&view=netframework-4.7.2#remarks