Lista de C # <> Ordenar por x luego y

88

Similar a List <> OrderBy Alphabetical Order , queremos ordenar por un elemento y luego por otro. queremos lograr el equivalente funcional de

SELECT * from Table ORDER BY x, y  

Tenemos una clase que contiene una serie de funciones de clasificación y no tenemos problemas para clasificar por un elemento.
Por ejemplo:

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

List<MyClass> MyList;

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

Y tenemos lo siguiente en la lista:

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

Sería preferible un tipo estable, pero no obligatorio. La solución que funcione para .Net 2.0 es bienvenida.

Byron Ross
fuente
@Bolu He eliminado explícitamente la etiqueta para hacer que la versión de publicación sea independiente y las respuestas actualizadas para que coincidan con eso. Considere hacer una edición aclaratoria en la pregunta en lugar de restaurar la etiqueta si cree que 4.0 / 2.0 no era lo suficientemente prominente.
Alexei Levenkov
Lo siento @AlexeiLevenkov, no le presté mucha atención, no dude en retroceder.
Bolu
OKAY. Revirtió el cambio.
Alexei Levenkov
Esta pregunta se actualizó para cubrir todas las versiones de .Net desde el original solo 2.0; contiene varias respuestas alternativas para diferentes marcos y requisitos; consulte todas para ver cuál se adapta mejor a sus requisitos.
Alexei Levenkov

Respuestas:

98

Tenga en cuenta que no necesita un tipo estable si compara todos los miembros. La solución 2.0, según lo solicitado, puede tener este aspecto:

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

Tenga en cuenta que esta solución 2.0 sigue siendo preferible a la popular solución 3.5 Linq, realiza una clasificación en el lugar y no tiene el requisito de almacenamiento O (n) del enfoque Linq. A menos que prefiera que el objeto List original esté intacto, por supuesto.

Hans Passant
fuente
156

Para las versiones de .Net donde puede usar LINQ OrderByy ThenBy(o ThenByDescendingsi es necesario):

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

Nota: para .Net 2.0 (o si no puede usar LINQ) vea la respuesta de Hans Passant a esta pregunta.

Toby
fuente
2
De otra publicación de respuesta de phoog aquí: stackoverflow.com/questions/9285426/… Crea otra lista con los elementos originales en un nuevo orden. Esto solo es útil si necesita conservar el orden original para algún otro propósito; es más un desperdicio de memoria que ordenar la lista en su lugar
dreamerkumar
5

El truco consiste en implementar un tipo estable. Creé una clase de widget que puede contener sus datos de prueba:

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

Implementé IComparable, por lo que List.Sort () puede ordenarlo de manera inestable.

Sin embargo, también implementé el método estático Compare, que se puede pasar como delegado a un método de búsqueda.

Tomé prestado este método de ordenación por inserción de 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;
            }
    }

Pondría esto en la clase de ayudantes de ordenación que mencionó en su pregunta.

Ahora, para usarlo:

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

Y da como resultado:

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

Esto probablemente se podría arreglar con algunos delegados anónimos, pero eso se lo dejo a usted.

EDITAR : Y NoBugz demuestra el poder de los métodos anónimos ... así que, considere el mío más antiguo: P

FlySwat
fuente
1

Tuve un problema en el que OrderBy y ThenBy no me dieron el resultado deseado (o simplemente no sabía cómo usarlos correctamente).

Fui con una lista. Clasifique la solución algo como esto.

    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
fuente