Desenvolvimento - C#

Ordenação de coleções em C#

Uma coleção admite a inserção de vários itens em seu interior, podendo acessar os mesmos através de seus índices ou chaves, em alguns casos...

por Paulo Henrique dos Santos Monteiro



Atualizado em 12/09/2008.

Uma coleção admite a inserção de vários itens em seu interior, podendo acessar os mesmos através de seus índices ou chaves, em alguns casos.

Estes dados são ordenados de acordo com a seqüência em que foram inseridos, como padrão.

Algumas categorias de coleção admitem ordenação explicita de seus dados, e o fazem através da exposição do método Sort.

O método Sort admite por padrão alguns overloads, vamos analisar os 2 mais prováveis.

Coleção.Sort() - Sem parâmetros, realiza uma operação de QuickSort interna, comparando binariamente os dados contidos na coleção.

Coleção.Sort(IComparer) - Usa como parâmetro uma instancia de uma classe que implemente a interface IComparer, de modo que se possa customizar o comportamento da ordenação.

De que maneira usa-se IComparer para customizar a ordenação?

Basicamente, um algoritmo de ordenação funciona comparando um item corrente com outro, verificando se o primeiro é menor, igual, ou maior que o segundo. De acordo com isto, o algoritmo mantém o mesmo no lugar ou muda-o com o segundo.

A interface IComparer possui um único método :

[C#]
int Compare(
   object x,
   object y
);

Que recebe (como era de se esperar, visto que uma coleção normalmente admite itens de qualquer tipo), dois objects, e retorna um inteiro < 0, caso o primeiro seja menor que o segundo, = 0 caso ambos sejam iguais, e > 0 caso o primeiro seja maior que o segundo. Aos que estão acostumados ao método CompareTo de vários objetos, como String, DateTime, etc, percebam que este é um padrão pré-estabelecido, e mais adiante mostrarei que isto é usual até mesmo fora do C#, como por exemplo no javascript, na ordenação de arrays.

Sendo assim, uma chamada a Sort passando uma instancia de uma classe que implemente IComparer irá repassar para a instancia da classe, para cada item comparado, os valores referentes ao item atual no loop interno, e esperar o resultado.

Um exemplo prático usando ArrayList

Imagine que você está carregando em um array de curso x professor de uma escola.

Iremos criar uma classe chamada Curso

public class Curso
	{
		public string NomeCurso;
		public string ProfessorCurso;
		
		public Curso(string NomeCurso, string ProfessorCurso)
		{
			this.NomeCurso = NomeCurso;
			this.ProfessorCurso = ProfessorCurso;
		}
		public override string ToString()
		{
			return NomeCurso + " ministrado por " + ProfessorCurso;
		}

	}

E iremos carregar um ArrayList com várias instâncias de Curso.

ArrayList objLista = new ArrayList();
objLista.Add(new Curso("Matematica","Périça"));
objLista.Add(new Curso("Artes","Jussara"));
objLista.Add(new Curso("OSPB","Rita Célia"));
objLista.Add(new Curso("Física","Marina"));
objLista.Add(new Curso("Geografia","Ércio"));
objLista.Add(new Curso("Literatura","Bernadete"));
objLista.Add(new Curso("Química","Carlos"));
objLista.Add(new Curso("Biologia","Márcia"));
objLista.Add(new Curso("Inglês","Doca"));
objLista.Add(new Curso("História","Luiz"));

Perceba que, se estivéssemos usando simples strings, aí o nosso método Sort sem parâmetros caberia como uma luva, pois internamente seria um simples QuickSort de strings.

Mas, e se quiséssemos ordenar a coleção por, digamos, Nome do Curso?

Para isto, devemos construir uma classe que implemente IComparer, e utilizá-la em nosso método Sort, e fornecer inteligência ao método Compare.

public class OrdenacaoPorCurso: IComparer  
{	int IComparer.Compare( Object x, Object y )  
	{
		Curso objetoA = (Curso) x;
		Curso objetoB = (Curso) y;
		return objetoA.NomeCurso.CompareTo(objetoB.NomeCurso) ;
	}
}

Perceba que, como o método recebe 2 objects como parâmetro, é muito simples o seu processamento. No caso, apenas converti via cast os 2 objects para Curso, e chamei simplesmente o método CompareTo de String (no caso, nomeCurso), que já me retorna um inteiro como é esperado.

E, caso eu quisesse fazer uma ordenação decrescente? Bastaria inverter o objeto de comparação.

public class OrdenacaoPorCursoDecrescente: IComparer  
{	int IComparer.Compare( Object x, Object y )  
	{
		Curso objetoA = (Curso) x;
		Curso objetoB = (Curso) y;
		return objetoB.NomeCurso.CompareTo(objetoA.NomeCurso) ;
	}
}

Ou ainda, ordenar por nome de professor:

public class OrdenacaoPorProfessor: IComparer  
{	int IComparer.Compare( Object x, Object y )  
	{
		Curso objetoA = (Curso) x;
		Curso objetoB = (Curso) y;
		return objetoA.ProfessorCurso.CompareTo(objetoB.ProfessorCurso) ;
	}
}

A chamada para ordenar por Curso seria simplesmente:

objLista.Sort(new OrdenacaoPorCurso());

E teríamos a lista ordenada. Variação sobre o mesmo tema: Ordenando uma ListView pelo conteúdo de uma coluna.

Uma ListView possui uma coleção de ListViewItems. Cada ListViewItem tem uma coleção de ListViewSubItems, que representam os valores texto exibidos em cada coluna.

Uma ListView não possui overload recebendo como parâmetro um IComparer, mas implementa a mesma funcionalidade de outra maneira.

Uma propriedade chamada Sorting indica qual a ordem da ordenação (SortOrder.Ascending, SortOrder.Descending ou SortOrder.None). Para implementar a ordenação via uma classe customizada, implementamos a mesma classe que implementa IComparer, mas ao invés de passá-la para Sort(), setamos a instancia a propriedade ListViewItemSorter.

Para ilustrar, vamos implementar um método genérico de ordenação, crescente ou descrescente, para toda a vez que o usuário clicar no caption de uma coluna de uma listview.

Para fazê-lo, a nossa classe de ordenação deve saber qual coluna vai ordenar, e qual o SortOrder, então vamos colocar as mesmas como parâmetros em nosso construtor, e armazená-las em variáveis privadas. O método Compare irá transformar os 2 parametros object em ListViewItem, e comparar o texto da coluna clicada do primeiro com o segundo ou vice-versa, de acordo com o SortOrder.

Eis o nosso código.

public class OrdenaListView: IComparer	
{
   private int col;
   private SortOrder sortOrder;
		
   public OrdenaListView(SortOrder sortOrder, int col)
   {
      this.sortOrder = sortOrder;
      this.col = col;
   }
		
   #region IComparer Members

   public int Compare(object x, object y)
   {
      ListViewItem objetoA = (ListViewItem) x;
      ListViewItem objetoB = (ListViewItem) y;
			
      if (this.sortOrder.Equals(SortOrder.Ascending))
         return objetoA.SubItems[this.col].Text.CompareTo(objetoB.SubItems[this.col].Text);
      else	
         return objetoB.SubItems[this.col].Text.CompareTo(objetoA.SubItems[this.col].Text);
   }
   #endregion
}

E o código do evento ColumnClick da ListView.

private void listView1_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
	this.listView1.Sorting = ((this.listView1.Sorting.Equals(SortOrder.Ascending)) ? 
SortOrder.Descending: SortOrder.Ascending );
	this.listView1.ListViewItemSorter = new OrdenaListView(this.listView1.Sorting,e.Column);
	this.listView1.Sort();
}

Ou seja, não fugimos muito do enunciado do artigo.

O uso de IComparer vale para todo algoritmo de ordenação que tenhamos. Você irá encontrar a mesma funcionalidade em diversos outros objetos.

Por curiosidade: Ordenação de arrays em javascript

Como eu havia mencionado, em javascript temos o objeto Array(), que é uma coleção também genérica, e como em C#, pode fazer uma ordenação simples via QuickSort ou usar uma função para a ordenação.

Assim como no C#, os valores esperados são <0 para o primeiro menor que o segundo, = 0 para ambos iguais, ou > 0 para o primeiro maior que o segundo.

Em javascript, podemos facilmente ordenar uma coleção usando uma função pré-construida, ou ainda declarar uma função no momento da sua utilização, através do objeto Function.

Assim, se uma coleção chamada colFeriados fosse ordenada, poderíamos fazer através da chamada a

funcSort = new Function("x","y","if (x.data==y.data) return 0;else if (x.data<y.data) return 
-1;else return 1;");

colFeriados = colFeriados.sort(funcSort);

Gostou da construção em tempo de execução de um método? Pois é isso que os métodos anônimos vão fazer no Visual Studio .NET 2005. Aguarde ...

» Baixe o código.

Paulo Henrique dos Santos Monteiro

Paulo Henrique dos Santos Monteiro - Tecnólogo formado pela Faculdade de Tecnologia da Baixada Santista FATEC/BS, com 20 anos de experiência comprovada na área, tendo atuado em praticamente todas as áreas, desde saúde, epidemiologia, até automação bancária, software básico, comércio, indústria, backoffice bancário, jogos, segurança, prestação de serviços e consultoria. Também ministrei aulas de programação, análise e tópicos avançados de programação em escolas de 2º. Grau técnico e cursos particulares.
Iniciou com Basic, Clipper, Assembly e C, passando pelo C++ (em Unix, OS/2, Windows e DOS), e depois desenvolvendo sistemas com Delphi, Visual Basic (conheço desde a obscura versão DOS, e atuo com VB desde o Beta da versão 1), Prolog, Pascal, ASP, Javascript, Java, e com .NET e ASP.Net desde o Beta da primeira versão.
Atua também como DBA, e é grande entusiasta da programação armazenada, em Sybase, Sql Server e Oracle.
Publica dicas e códigos no seu blog,
http://taotecnologia.blogspot.com.