web-dev-qa-db-fra.com

Utiliser IComparer pour le tri

J'essaie d'utiliser une IComparer pour trier une liste de points. Voici la classe 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;
    }
}

Dans le code client, j'essaie d'utiliser cette classe pour trier une liste de points p (de type List<Point>):

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

Le code est erroné. Apparemment, il attend IComparer<Point> comme argument de la méthode de tri.
Que dois-je faire pour résoudre ce problème?

29
Aadith

Vous devez implémenter l'interface de type fortement ( 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;
    }
}

BTW, je pense que vous utilisez trop d'accolades, je pense qu'ils ne devraient être utilisés que lorsqu'ils contribuent au compilateur. Ceci est ma version:

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;

Tout comme je n'aime pas les gens qui utilisent return (0).


Notez que si vous ciblez une application .Net-3.5 +, vous pouvez utiliser LINQ, qui est plus facile et encore plus rapide avec le tri.

LINQ vesion peut être quelque chose comme:

var orderedList = Points.OrderBy(point => point.x)
                        .ThenBy(point => point.y)
                        .ToList();
41
gdoron
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);            
    }
}
12
Marc Gravell

Si vous êtes lent comme moi, les valeurs -1 et 1 peuvent être difficiles à raisonner lorsque vous utilisez IComparer. La façon de penser à cela est de savoir quand x devrait commencer, renvoyer -1. Lorsque y doit commencer, renvoyer 1.

Cela peut encore être déroutant si vous devez trier beaucoup de champs. Vous pouvez utiliser une variable Enum pour rendre votre logique de comparaison plus lisible que 1 et -1, puis transtyper le résultat.

Cet exemple place les objets avec le moins de champs nuls au premier plan.

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; }
}
5
Chad Hedgcock

J'obtenais une erreur InvalidOperation lors de l'ajout d'un objet de type MyClass à un SortedList<MyClass>. J'étais, à tort, en train de mettre en œuvre l'interface IComparer. Ce que je devais mettre en œuvre était IComparable avec la méthode CompareTo (MyClass other), au lieu de ICompare.Compare (MyClass x, MyClass y). Ceci est un exemple simplifié:

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

Cela a résolu le problème :

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

Ceci était cassé (ne le faites pas si vous ajoutez à SortedList<MyClass>) :

public class MyClass : IComparer<MyClass>
{
    int IComparable<MyClass>.Compare(MyClass x, MyClass y)
    {
        // DoCompareFunction(x, y); and return -1,0,1
    }
}

C'était l'erreur:

Échec de la comparaison de deux éléments dans le tableau.
sur System.Collections.Generic.ArraySortHelper`1.BinarySearch (tableau T [], Int32, longueur Int32, valeur T, IComparer`1 comparer)
at System.Array.BinarySearch [T] (tableau T [], index Int32, longueur Int32, valeur T , IComparer`1 comparer)
sur System.Collections.Generic.SortedList`2.Add (clé TKey, valeur TValue)

0
Jason Hitchings