Liste C # <> Trier par x puis y

88

Similaire à List <> OrderBy Alphabetical Order , nous voulons trier par un élément, puis un autre. nous voulons atteindre l'équivalent fonctionnel de

SELECT * from Table ORDER BY x, y  

Nous avons une classe qui contient un certain nombre de fonctions de tri et nous n'avons aucun problème à trier par un élément.
Par exemple:

public class MyClass {
    public int x;
    public int y;
}  

List<MyClass> MyList;

public void SortList() {
    MyList.Sort( MySortingFunction );
}

Et nous avons les éléments suivants dans la liste:

Unsorted     Sorted(x)     Desired
---------    ---------    ---------
ID   x  y    ID   x  y    ID   x  y
[0]  0  1    [2]  0  2    [0]  0  1
[1]  1  1    [0]  0  1    [2]  0  2
[2]  0  2    [1]  1  1    [1]  1  1
[3]  1  2    [3]  1  2    [3]  1  2

Un tri stable serait préférable, mais pas obligatoire. La solution qui fonctionne pour .Net 2.0 est la bienvenue.

Byron Ross
la source
@Bolu J'ai explicitement supprimé la balise pour rendre les réponses indépendantes de la version de publication et mis à jour les réponses pour correspondre à cela. Pensez à apporter une clarification à la question au lieu de restaurer la balise si vous pensez que 4.0 / 2.0 n'était pas suffisamment visible.
Alexei Levenkov
Désolé @AlexeiLevenkov, je n'ai pas fait très attention, n'hésitez pas à revenir en arrière.
Bolu
D'ACCORD. Annulé le changement.
Alexei Levenkov
Cette question a été mise à jour pour couvrir toutes les versions de .Net à partir de la version originale seulement 2.0 - contient plusieurs réponses alternatives pour différents cadres et exigences - vérifiez tout pour voir celle qui correspond le mieux à vos exigences.
Alexei Levenkov

Réponses:

98

Gardez à l'esprit que vous n'avez pas besoin d'un tri stable si vous comparez tous les membres. La solution 2.0, comme demandé, peut ressembler à ceci:

 public void SortList() {
     MyList.Sort(delegate(MyClass a, MyClass b)
     {
         int xdiff = a.x.CompareTo(b.x);
         if (xdiff != 0) return xdiff;
         else return a.y.CompareTo(b.y);
     });
 }

Notez que cette solution 2.0 est toujours préférable à la solution 3.5 Linq populaire, elle effectue un tri sur place et n'a pas l'exigence de stockage O (n) de l'approche Linq. À moins que vous ne préfériez bien sûr que l'objet List d'origine reste intact.

Hans Passant
la source
156

Pour les versions de .Net où vous pouvez utiliser LINQ OrderByet ThenBy(ou ThenByDescendingsi nécessaire):

using System.Linq;
....
List<SomeClass>() a;
List<SomeClass> b = a.OrderBy(x => x.x).ThenBy(x => x.y).ToList();

Remarque: pour .Net 2.0 (ou si vous ne pouvez pas utiliser LINQ) voir la réponse de Hans Passant à cette question.

Toby
la source
2
À partir d'un autre message de réponse par phoog ici: stackoverflow.com/questions/9285426/… Il crée une autre liste avec les éléments originaux dans un nouvel ordre. Cela n'est utile que si vous avez besoin de conserver la commande d'origine à d'autres fins; c'est plutôt un gaspillage de mémoire que de trier la liste sur place
dreamerkumar
5

L'astuce consiste à implémenter un tri stable. J'ai créé une classe Widget qui peut contenir vos données de test:

public class Widget : IComparable
{
    int x;
    int y;
    public int X
    {
        get { return x; }
        set { x = value; }
    }

    public int Y
    {
        get { return y; }
        set { y = value; }
    }

    public Widget(int argx, int argy)
    {
        x = argx;
        y = argy;
    }

    public int CompareTo(object obj)
    {
        int result = 1;
        if (obj != null && obj is Widget)
        {
            Widget w = obj as Widget;
            result = this.X.CompareTo(w.X);
        }
        return result;
    }

    static public int Compare(Widget x, Widget y)
    {
        int result = 1;
        if (x != null && y != null)                
        {                
            result = x.CompareTo(y);
        }
        return result;
    }
}

J'ai implémenté IComparable, donc il peut être trié de manière instable par List.Sort ().

Cependant, j'ai également implémenté la méthode statique Compare, qui peut être transmise en tant que délégué à une méthode de recherche.

J'ai emprunté cette méthode de tri par insertion à C # 411 :

 public static void InsertionSort<T>(IList<T> list, Comparison<T> comparison)
        {           
            int count = list.Count;
            for (int j = 1; j < count; j++)
            {
                T key = list[j];

                int i = j - 1;
                for (; i >= 0 && comparison(list[i], key) > 0; i--)
                {
                    list[i + 1] = list[i];
                }
                list[i + 1] = key;
            }
    }

Vous mettriez cela dans la classe des aides au tri que vous avez mentionnée dans votre question.

Maintenant, pour l'utiliser:

    static void Main(string[] args)
    {
        List<Widget> widgets = new List<Widget>();

        widgets.Add(new Widget(0, 1));
        widgets.Add(new Widget(1, 1));
        widgets.Add(new Widget(0, 2));
        widgets.Add(new Widget(1, 2));

        InsertionSort<Widget>(widgets, Widget.Compare);

        foreach (Widget w in widgets)
        {
            Console.WriteLine(w.X + ":" + w.Y);
        }
    }

Et il sort:

0:1
0:2
1:1
1:2
Press any key to continue . . .

Cela pourrait probablement être nettoyé avec des délégués anonymes, mais je vous laisse le soin.

EDIT : Et NoBugz démontre la puissance des méthodes anonymes ... alors, considérez la mienne plus oldschool: P

FlySwat
la source
1

J'ai eu un problème où OrderBy et ThenBy ne m'ont pas donné le résultat souhaité (ou je ne savais tout simplement pas comment les utiliser correctement).

Je suis allé avec une solution list.Sort quelque chose comme ça.

    var data = (from o in database.Orders Where o.ClientId.Equals(clientId) select new {
    OrderId = o.id,
    OrderDate = o.orderDate,
    OrderBoolean = (SomeClass.SomeFunction(o.orderBoolean) ? 1 : 0)
    });

    data.Sort((o1, o2) => (o2.OrderBoolean.CompareTo(o1.OrderBoolean) != 0
    o2.OrderBoolean.CompareTo(o1.OrderBoolean) : o1.OrderDate.Value.CompareTo(o2.OrderDate.Value)));
mofoo
la source