使用 IComparer 进行排序

54

我正在尝试使用一个 IComparer 对 Point 类型的列表进行排序。以下是 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<Point>)进行排序:
CoordinatesBasedComparer c = new CoordinatesBasedComparer();
Points.Sort(c);

代码出错了。显然,它期望将 IComparer<Point> 作为参数传递给 sort 方法。
我需要做什么来解决这个问题?


你遇到了什么错误?在哪一行? - O. R. Mapper
1
为什么不使用LINQ呢?它在排序方面甚至更快。 - gdoron
7
@gdoron 我不确定“更快”是正确的术语;也许应该用“更方便”。 - Marc Gravell
@MarcGravell,我测试了几次,速度更快。想一想,使用LINQ时,您知道列表何时已排序,而使用IComparer则不知道。更快。 - gdoron
4个回答

68

您需要实现强类型接口 (MSDN)。

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> 时,Point 是否作为占位符变量值,需要在实例化期间指定值?有点困惑...如果它所工作的类不需要在客户端指定,那么拥有泛型类型的意义何在? - Aadith Ramia
@Aadith,你让我有点困惑了....接口:“IComparer<T>”,让你选择要用哪个类替换“T”,在你的情况下,你使用了“Point”。就像当你创建一个泛型点列表:“List<Point>”,这是“List < T>”时,“T”为点。 - gdoron
1
请注意,LINQ 版本与非 LINQ 版本不同之处在于 List<Point> 实例被替换。如果重要的是列表仍然是相同的列表,那么应该注意这一点。 - O. R. Mapper
就像每个LINQ方法一样,我从未遇到过与之相关的问题,除非明确要求保留原始的List<T>,否则我不觉得有必要进行指定。@O.R.Mapper - gdoron
3
顺便说一句,我认为你使用了太多的大括号,我相信它们只有在对编译器有贡献时才应该使用。大括号有助于避免错误。https://www.imperialviolet.org/2014/02/22/applebug.html - James South
显示剩余4条评论

14
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);            
    }
}

2
如果OP只需要泛型版本,那么在这里实现非泛型版本的IComparer有什么意义呢? - zmbq
4
因为当编写比较器时,您不知道调用方是谁以及他们需要什么。另外,这只涉及一行实际代码和一个方法签名。 - Marc Gravell
+1,仅出于好奇:自从LINQ进入游戏(在.Net3.5\C#3...中)以来,你是否使用过IComparer,IComparer<Point> - gdoron

12

如果您像我一样慢,使用 IComparer 时,-1 和 1 可能很难理解。思考方式是当要让 x 放在第一位时,返回 -1。当要让 y 放在第一位时,返回 1。

如果有很多字段需要排序,仍然可能会令人困惑。您可以使用一个 Enum 来使比较逻辑比 1 和 -1 更易读,并转换结果。

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

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; }
}

这是错误的。当 x < y 时,应返回 -1,当 x > y 时,应返回 1。您可以通过在 int/double 上调用 CompareTo 来检查它。 - bombek
2
没错,这就相当于说“当 x 应该先走时,返回 -1。当 y 应该先走时,返回 1。” - Chad Hedgcock
1
如果你像我一样慢,使用IComparer时-1和1可能很难理解。思考的方法是当x应该先出现时,返回-1。当y应该先出现时,返回1。我的错。结果是相同的,但过程采用了不同的方法。基本上你所做的是从不同的方面进行排序。这不是我喜欢的方式。我只是被这篇文章误导了。 - bombek

0
我在向一个 SortedList<MyClass> 添加一个 MyClass 类型对象时遇到了 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<MyClass>中,请不要这样做)

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[] array, Int32 index, Int32 length, T value, IComparer`1 comparer) 处发生错误。
在 System.Array.BinarySearch[T](T[] array, Int32 index, Int32 length, T value, IComparer`1 comparer) 处发生错误。
在 System.Collections.Generic.SortedList`2.Add(TKey key, TValue value) 处发生错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接