使用 IComparer 进行排序

Posted

技术标签:

【中文标题】使用 IComparer 进行排序【英文标题】:Using IComparer for sorting 【发布时间】:2012-12-29 11:22:09 【问题描述】:

我正在尝试使用IComparer 对点列表进行排序。这是 IComparer 类:

public class CoordinatesBasedComparer : IComparer

    public int Compare(Object q, Object r)
    
        Point a = (p)q;
        Point b = (p)r;
        if ((a.x == b.x) && (a.y == b.y))
            return 0;
        if ((a.x < b.x) || ((a.x == b.x) && (a.y < b.y)))
            return -1;

        return 1;
    

在客户端代码中,我尝试使用此类对点 p 列表(List&lt;Point&gt; 类型)进行排序:

CoordinatesBasedComparer c = new CoordinatesBasedComparer();
Points.Sort(c);

代码出错了。显然,它期望 IComparer&lt;Point&gt; 作为 sort 方法的参数。 我需要做什么来解决这个问题?

【问题讨论】:

您遇到什么错误?在哪一行? 为什么不使用 LINQ,排序更快。 @gdoron 我不确定“更快”是否正确; 更方便,也许 @MarcGravell,我测试了几次,速度更快。想想看,使用 LINQ,您知道列表何时排序,而使用 IComparer 您不知道。更快。 【参考方案1】:

你需要实现强类型接口(@98​​7654321@)。

public class CoordinatesBasedComparer : IComparer<Point>

    public int Compare(Point a, Point b)
    
        if ((a.x == b.x) && (a.y == b.y))
            return 0;
        if ((a.x < b.x) || ((a.x == b.x) && (a.y < b.y)))
            return -1;

        return 1;
    

顺便说一句,我认为您使用了太多大括号,我认为只有在它们对编译器有贡献时才应该使用它们。这是我的版本:

if (a.x == b.x && a.y == b.y)
    return 0;
if (a.x < b.x || (a.x == b.x && a.y < b.y))
    return -1;

就像我不喜欢使用return (0)的人一样。


请注意,如果您以 .Net-3.5+ 应用程序为目标,则可以使用 LINQ,它更容易进行排序,甚至更快。

LINQ 版本可以类似于:

var orderedList = Points.OrderBy(point => point.x)
                        .ThenBy(point => point.y)
                        .ToList();

【讨论】:

当您说 IComparer 时,Point 不是作为占位符变量值,在实例化期间必须为其指定值吗?有点困惑...如果不需要在客户端指定它所围绕的类,那么拥有泛型类型有什么意义? @Aadith,你让我很困惑......不,界面:IComparer&lt;T&gt; 让您选择要替换的类T,在您的情况下,您使用了Point .就像创建点的通用列表时一样:List&lt;Point&gt; 当 T 为点时为 List&lt;T&gt; 请注意,LINQ 版本与非 LINQ 版本的不同之处在于替换了 List&lt;Point&gt; 实例。如果列表保持相同列表很重要,则应注意这一点。 @O.R.Mapper。就像每个 LINQ 方法一样,事实上我从来没有遇到过它的问题,我觉得没有必要指定它,除非明确要求保留原来的 List&lt;T&gt; >顺便说一句,我认为您使用了太多大括号,我认为只有在它们对编译器有贡献时才应该使用它们。大括号有助于避免错误。 imperialviolet.org/2014/02/22/applebug.html【参考方案2】:
public class CoordinatesBasedComparer : IComparer, IComparer<Point>

    public int Compare(Point a, Point b)
    
        if ((a.x == b.x) && (a.y == b.y))
            return 0;
        if ((a.x < b.x) || ((a.x == b.x) && (a.y < b.y)))
            return -1;

        return 1;
    
    int IComparer.Compare(Object q, Object r)
    
        return Compare((Point)q, (Point)r);            
    

【讨论】:

如果所有 OP 需要的是通用版本,那么在这里实现 IComparer(非通用版本)有什么意义? @zmbq a:因为在编写比较器时,您不知道调用者是谁,以及他们需要什么。而b:因为它是1行实际代码,加上一个方法签名 +1,只是出于好奇:自从LINQ进入游戏以来,您使用过IComparer, IComparer&lt;Point&gt;吗?(在.Net3.5\C#3中...)【参考方案3】:

如果你像我一样慢,在使用IComparer 时,-1 和 1 可能很难理解。考虑它的方法是当x 应该先走时,返回-1。当y 应该在前时,返回 1。

如果您有很多字段要排序,它仍然会让人感到困惑。您可以使用Enum 使您的比较逻辑比 1 和 -1 更具可读性,然后转换结果。

此示例将具有最少 null 字段的对象放在前面。

public class NullishObjectsToTheBackOfTheLine: IComparer<ClassToCompare>

    private enum Xy
    
        X = -1,
        Both = 0,
        Y = 1
    ;

    //the IComparer implementation wraps your readable code in an int cast.
    public int Compare(ClassToCompare x, ClassToCompare y)
    
        return (int) CompareXy(x, y);
    

    private static Xy CompareXy(ClassToCompare x, ClassToCompare y)
    
        if (x == null && y == null) return Xy.Both;

        //put any nulls at the end of the list
        if (x == null) return Xy.Y;
        if (y == null) return Xy.X;

        if (x.Country == y.Country && x.ProductId == y.ProductId) return Xy.Both;

        //put the least amount of at the front
        if (x.ProductId == null && x.Country == null) return Xy.Y;
        if (y.ProductId == null && y.Country == null) return Xy.X;

        //put the country values that are not nulls in front
        if (x.Country != y.Country) return x.Country != null ? Xy.X :  Xy.Y;

        //if we got this far, one of these has a null product id and the other doesn't
        return x.ProductId != null ? Xy.X : Xy.Y;
    



public class ClassToCompare

    public string Country  get; set; 
    public string ProductId  get; set; 

【讨论】:

这是错误的。 1 表示 x > y,-1 表示 x 没有错,这就是说“当x应该先走时,返回-1。当 y 应该先走时,返回 1。” @ChadHedgock If you're slow like me, the -1 and 1 can be difficult to reason about when using IComparer. The way to think about it is when x should go first, return -1. When y should go first, return 1. 。过失。结果是相同的,但旅程遵循不同的方法。基本上你所做的就是从不同的方面进行排序。我不喜欢。我只是被这篇文章误导了。【参考方案4】:

我在将MyClass 类型的对象添加到SortedList&lt;MyClass&gt; 时收到InvalidOperation 错误。我错误地实现了 IComparer 接口。我需要实现的是 IComparable 与方法 CompareTo(MyClass other),而不是 ICompare.Compare(MyClass x, MyClass y)。这是一个简化的例子:

SortedList<MyClass> sortedList = new SortedList<MyClass>();
MyClass a=new MyClass(), b=new MyClass();
sortedList.Add(a);
sortedList.Add(b); // Note, sort only happens once second element is added

这修复了它

public class MyClass : IComparable<MyClass>

    int IComparable<MyClass>.CompareTo(MyClass other)
    
        // DoCompareFunction(this, other); and return -1,0,1
    

这被破坏了(如果添加到SortedList&lt;MyClass&gt;,请不要这样做)

public class MyClass : IComparer<MyClass>

    int IComparable<MyClass>.Compare(MyClass x, MyClass y)
    
        // DoCompareFunction(x, y); and return -1,0,1
    

这是错误:

无法比较数组中的两个元素。 在 System.Collections.Generic.ArraySortHelper`1.BinarySearch(T[] 数组, Int32 索引,Int32 长度,T 值,IComparer`1 比较器) 在 System.Array.BinarySearch[T](T[] 数组,Int32 索引,Int32 长度,T 值,IComparer`1 比较器) 在 System.Collections.Generic.SortedList`2.Add(TKey key, TValue value)

【讨论】:

以上是关于使用 IComparer 进行排序的主要内容,如果未能解决你的问题,请参考以下文章

C# 中的 Icompare 接口和 IComparable 接口有啥区别??

[译]在C#中使用IComparable和IComparer接口

使用 IComparer 进行排序

HashSet使用自定义IComparer排序而不使用LINQ

OrderBy排序和IComparer的使用

IComparer<> 和 C# 中的类继承