Lista C # <> Classificar por x e depois por y


88

Semelhante a List <> OrderBy Alphabetical Order , queremos classificar por um elemento e depois por outro. queremos alcançar o equivalente funcional de

SELECT * from Table ORDER BY x, y  

Temos uma classe que contém várias funções de classificação e não temos problemas para classificar por um elemento.
Por exemplo:

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

List<MyClass> MyList;

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

E temos o seguinte na 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

A classificação estável seria preferível, mas não necessária. Solução que funciona para .Net 2.0 é bem-vinda.


@Bolu Eu removi explicitamente a tag para tornar a versão da postagem agnóstica e as respostas atualizadas para corresponder a isso. Considere fazer uma edição esclarecedora na questão em vez de restaurar a tag se você acha que 4.0 / 2.0 não era proeminente o suficiente.
Alexei Levenkov

Desculpe @AlexeiLevenkov, não prestei muita atenção, fique à vontade para reverter.
Bolu

ESTÁ BEM. Reverteu a mudança.
Alexei Levenkov

Esta pergunta foi atualizada para cobrir todas as versões do .Net do original apenas 2.0 - contém várias respostas alternativas para diferentes estruturas e requisitos - verifique todas para ver qual se encaixa melhor nos seus requisitos.
Alexei Levenkov,

Respostas:


98

Tenha em mente que você não precisa de uma classificação estável se comparar todos os membros. A solução 2.0, conforme solicitado, pode ser assim:

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

Observe que essa solução 2.0 ainda é preferível à popular solução 3.5 Linq, ela executa uma classificação local e não tem o requisito de armazenamento O (n) da abordagem Linq. A menos que você prefira que o objeto List original seja intocado, é claro.


156

Para versões do .Net em que você pode usar LINQ OrderBye ThenBy(ou ThenByDescendingse necessário):

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

Observação: para .Net 2.0 (ou se você não puder usar o LINQ), consulte a resposta de Hans Passant a esta pergunta.


2
De outra postagem de resposta por phoog aqui: stackoverflow.com/questions/9285426/… Ele cria outra lista com os itens originais em uma nova ordem. Isso só é útil se você precisar preservar a ordem original para algum outro propósito;
gasta


5

O truque é implementar uma classificação estável. Eu criei uma classe Widget que pode conter seus dados de teste:

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

Implementei IComparable, para que possa ser classificado de forma instável por List.Sort ().

No entanto, também implementei o método estático Compare, que pode ser passado como um delegado para um método de pesquisa.

Peguei emprestado este método de classificação por inserção do 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;
            }
    }

Você colocaria isso na classe de auxiliares de classificação que mencionou em sua pergunta.

Agora, para usá-lo:

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

E produz:

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

Isso provavelmente poderia ser resolvido com alguns delegados anônimos, mas vou deixar isso para você.

EDIT : E NoBugz demonstra o poder dos métodos anônimos ... então, considere o meu mais antigo: P



1

Tive um problema em que OrderBy e ThenBy não me deram o resultado desejado (ou simplesmente não sabia como usá-los corretamente).

Fui com uma solução list.Sort algo assim.

    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)));
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.