C# A Classe Array (Sort, IndexOf…)
Na aula Arrays, a Ana escreveu à mão os laços para achar o maior valor e para procurar um código. Funciona, mas há tarefas que todo programa faz e que dão bem mais trabalho, como colocar em ordem. O .NET já traz essas ferramentas prontas, testadas e rápidas, numa caixa de ferramentas chamada Array: Array.Sort ordena, Array.Reverse inverte, Array.IndexOf procura, Array.Fill enche… Nesta aula você vai conhecer as principais, ver a saída real de cada uma e as pegadinhas, como a de que o Sort muda o array original.
- O que é a classe
Arraye como se chama um método dela:Array.Sort(notas). - Ordenar números e textos (com acentos e maiúsculas, no pt-BR), inverter, e ordenar do maior para o menor.
- Procurar:
IndexOf,LastIndexOf,Containse a busca binária,BinarySearch. - Encher, zerar e copiar:
Fill,Clear,CopyeClone. - Mudar o tamanho com
Array.Resize(ref …). - Achar por uma condição:
Find,FindIndex,FindAll,ExistseTrueForAll. - Os erros reais:
CS0029comvoid,CS1620eArgumentException.
Todos os programas foram rodados com o .NET 10 (dotnet --version = 10.0.112, C# 14) na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs.
A ideia: a caixa de ferramentas da oficina
O array é a prateleira com as peças. A classe Array é a caixa de ferramentas pendurada na parede da oficina: ela não guarda peça nenhuma, só ferramentas que trabalham em qualquer prateleira que você trouxer. Por isso se escreve o nome da caixa, um ponto e a ferramenta, e entre parênteses a prateleira em que ela vai trabalhar:
Array.Sort(notas); // │ │ └── em qual array // │ └─────── a ferramenta (o método) // └──────────── a caixa (a classe Array)
Esses métodos chamados pelo nome da classe são métodos estáticos (aula static). Você não precisa criar nada: a caixa já está na parede. Ela vem do System, que todo programa já enxerga.
1. Ordenar: Array.Sort e Array.Reverse
int[] notas = [7, 4, 9, 6, 8];
Console.WriteLine($"Antes: {string.Join(", ", notas)}");
Array.Sort(notas);
Console.WriteLine($"Depois: {string.Join(", ", notas)}");
Array.Reverse(notas);
Console.WriteLine($"Ao contrário: {string.Join(", ", notas)}");
- Array.Sort(notas);
- "Ordenar": põe os elementos em ordem crescente, do menor para o maior. Ele muda o próprio array: não devolve um array novo, devolve nada (
void). - Array.Reverse(notas);
- "Inverter": vira o array de trás para a frente. Depois de um
Sort, isso dá a ordem decrescente.
$ dotnet run ordenar.cs Antes: 7, 4, 9, 6, 8 Depois: 4, 6, 7, 8, 9 Ao contrário: 9, 8, 7, 6, 4
Sort muda o original, e não há volta
Depois do Array.Sort(notas), a ordem antiga sumiu. Se você ainda precisa dela (por exemplo, a ordem em que os alunos estão na chamada), ordene uma cópia: int[] ordenadas = [.. notas]; Array.Sort(ordenadas);. Só int[] ordenadas = notas; não serve: as duas variáveis apontariam para o mesmo array (seção "Quando dá errado").
Ordenar textos
Com string, o Sort usa a ordem do dicionário do idioma do computador. No notebook da Ana, em pt-BR, isso quer dizer que acentos e maiúsculas não bagunçam a ordem:
string[] nomes = ["bia", "Caio", "Ana", "Álvaro", "ana", "Davi", "Érica", "Eduardo"];
Array.Sort(nomes);
Console.WriteLine(string.Join(", ", nomes));
Array.Sort(nomes, StringComparer.Ordinal);
Console.WriteLine(string.Join(", ", nomes));
$ dotnet run nomes.cs Álvaro, ana, Ana, bia, Caio, Davi, Eduardo, Érica Ana, Caio, Davi, Eduardo, ana, bia, Álvaro, Érica
- Array.Sort(nomes);
- A ordem de dicionário (pt-BR): o
Álvarofica entre os "a", oÉricadepois doEduardo(o "r" vem depois do "d"), eanaeAnaficam juntos. É o que uma pessoa espera. - StringComparer.Ordinal
- "Comparação ordinal", pela ordem dos códigos das letras: todas as maiúsculas (A–Z) vêm antes de todas as minúsculas (a–z), e as letras com acento, que têm códigos maiores, vão para o fim. É mais rápida e não depende do idioma, mas raramente é o que se quer mostrar para uma pessoa.
O editor do guia ordena do mesmo jeito que o notebook da Ana. A aula Cultura mostra por que um computador em outro idioma pode ordenar diferente.
Missão: a lista de compras da Ana está fora de ordem. Ordene com Array.Sort antes de mostrar. A saída tem de ser Compras: arroz, café, feijão, leite, óleo.
Compras: arroz, café, feijão, leite, óleo
string[] compras = ["leite", "óleo", "arroz", "feijão", "café"];
Console.WriteLine($"Compras: {string.Join(", ", compras)}");
string[] compras = ["leite", "óleo", "arroz", "feijão", "café"];
Array.Sort(compras);
Console.WriteLine($"Compras: {string.Join(", ", compras)}");
Do maior para o menor
Há dois jeitos. O mais simples é ordenar e inverter. O outro é dizer ao Sort como comparar, com uma lambda (uma função curta, escrita ali mesmo; aula Lambdas):
int[] pontos = [12, 45, 7, 30, 18, 40];
Array.Sort(pontos, (a, b) => b.CompareTo(a));
Console.WriteLine(string.Join(", ", pontos));
$ dotnet run desc2.cs 45, 40, 30, 18, 12, 7
- (a, b) => b.CompareTo(a)
- "Dados dois elementos
aeb, comparebcoma". OCompareTodevolve um número negativo, zero ou positivo, conforme o primeiro seja menor, igual ou maior. Normalmente seriaa.CompareTo(b)(crescente); trocar a ordem parab.CompareTo(a)inverte o resultado. Se lambda ainda parece grego, useSort+Reverse: dá o mesmo.
Ordenar dois arrays juntos
A lojinha guarda os produtos num array e os preços em outro, na mesma posição. Para ordenar pelo preço sem desencontrar os nomes, o Sort aceita dois arrays: ordena o primeiro e faz as mesmas trocas no segundo.
string[] produtos = ["café", "arroz", "leite"];
decimal[] precos = [18.90m, 6.50m, 4.99m];
Array.Sort(precos, produtos);
for (int i = 0; i < produtos.Length; i++)
{
Console.WriteLine($"{produtos[i],-6} {precos[i],8:C}");
}
$ dotnet run juntos.cs leite R$ 4,99 arroz R$ 6,50 café R$ 18,90
- Array.Sort(precos, produtos);
- O primeiro é a chave (o que manda na ordem); o segundo acompanha. O leite, mais barato, foi para a frente, levando o nome junto.
- {produtos[i],-6}
- Com o número negativo, o texto ocupa 6 espaços encostado à esquerda; com
8:C, o preço ocupa 8 espaços à direita, em reais (aula Formatar Números).
Guardar dados que andam juntos em arrays separados é frágil. Mais adiante, você vai juntar nome e preço numa coisa só (Records, Classes) e ordenar com o LINQ (OrderBy).
2. Procurar: IndexOf, LastIndexOf, Contains
string[] compras = ["arroz", "feijão", "café", "leite", "café"];
Console.WriteLine(Array.IndexOf(compras, "café"));
Console.WriteLine(Array.LastIndexOf(compras, "café"));
Console.WriteLine(Array.IndexOf(compras, "pão"));
Console.WriteLine(Array.IndexOf(compras, "Café"));
Console.WriteLine(compras.Contains("leite"));
$ dotnet run buscar.cs 2 4 -1 -1 True
- Array.IndexOf(compras, "café")
- "Índice de": a posição da primeira vez que o valor aparece: 2. É a busca linear da aula Arrays, pronta.
- Array.LastIndexOf(…)
- A posição da última vez: 4.
- −1
- "Não achei". O
IndexOfnão dá erro quando o valor não está lá: devolve −1, que não é um índice válido. Confira antes de usar o resultado como índice. - "Café" → −1
- A busca é exata: maiúscula conta.
"Café"e"café"são textos diferentes. - compras.Contains("leite")
- "Contém?": só
TrueouFalse. Ele não é da classeArray: é do LINQ (aula O que é LINQ), e por isso se escreve depois do array, com ponto. Funciona em qualquer programa de arquivo único.
Missão: use Array.IndexOf para descobrir em que posição está o "café", no lugar do -1. A saída tem de ser café está na posição 2.
café está na posição 2
string[] compras = ["arroz", "feijão", "café", "leite"];
int posicao = -1; // troque pelo Array.IndexOf
Console.WriteLine($"café está na posição {posicao}");
string[] compras = ["arroz", "feijão", "café", "leite"];
int posicao = Array.IndexOf(compras, "café");
Console.WriteLine($"café está na posição {posicao}");
Busca binária: Array.BinarySearch
O IndexOf olha um por um. Num array em ordem, dá para ser muito mais esperto, como quem procura uma palavra no dicionário: abre no meio, vê se a palavra está antes ou depois, e descarta metade de uma vez. Num array de um milhão de elementos, isso acha qualquer valor em no máximo 20 olhadas.
int[] codigos = [101, 205, 330, 412, 587]; Console.WriteLine(Array.BinarySearch(codigos, 412)); Console.WriteLine(Array.BinarySearch(codigos, 400)); int[] baguncado = [412, 101, 587, 330, 205]; Console.WriteLine(Array.BinarySearch(baguncado, 330));
$ dotnet run busca-bin.cs 3 -4 « não achou: qualquer número negativo quer dizer "não está" -1 « o 330 está lá, mas o array não estava em ordem!
O 412 está na posição 3. Para o 400, que não existe, veio um número negativo (o −4 ainda diz, de um jeito codificado, onde o 400 entraria). E no array fora de ordem, o 330 está lá (posição 3), mas a busca binária disse que não: ela só funciona em arrays ordenados com Array.Sort. Regra: BinarySearch só depois de Sort, e o teste de "não achou" é < 0, não == -1.
3. Encher, zerar e copiar
int[] placar = new int[5];
Array.Fill(placar, 10);
Console.WriteLine(string.Join(" ", placar));
Array.Clear(placar);
Console.WriteLine(string.Join(" ", placar));
char[] linha = new char[8];
Array.Fill(linha, '-');
Console.WriteLine(new string(linha));
$ dotnet run encher.cs 10 10 10 10 10 0 0 0 0 0 --------
- Array.Fill(placar, 10);
- "Encher": põe o mesmo valor em todas as posições. Todo jogador começa com 10 pontos.
- Array.Clear(placar);
- "Limpar": volta todas as posições ao valor padrão (0,
falseounull). - new string(linha)
- Um array de
charvira um texto: oito tracinhos, uma linha para separar partes de um relatório.
Copiar: Array.Copy e Clone
int[] origem = [1, 2, 3, 4, 5];
int[] destino = new int[7];
Array.Copy(origem, destino, 3);
Console.WriteLine(string.Join(" ", destino));
Array.Copy(origem, 1, destino, 4, 3);
Console.WriteLine(string.Join(" ", destino));
int[] clone = (int[])origem.Clone();
clone[0] = 99;
Console.WriteLine($"{origem[0]} {clone[0]}");
$ dotnet run copiar.cs 1 2 3 0 0 0 0 1 2 3 0 2 3 4 1 99
- Array.Copy(origem, destino, 3);
- Copia os 3 primeiros elementos de
origempara o começo dedestino. Os dois arrays já precisam existir. - Array.Copy(origem, 1, destino, 4, 3);
- A forma completa: "da
origem, a partir da posição 1, para odestino, a partir da posição 4, copie 3 elementos". Copiou o 2, o 3 e o 4 para as posições 4, 5 e 6. - (int[])origem.Clone()
- "Clonar": um array novo, igual. O
Clonedevolve umobjectgenérico, e o(int[])diz ao C# que é umint[](aula Conversão de Tipos). Mudar o clone não muda a origem. No C# moderno,int[] clone = [.. origem];faz o mesmo, sem o(int[]).
4. Mudar o tamanho: Array.Resize
Um array não cresce. O Array.Resize dá a impressão de que cresce: ele cria um array novo do tamanho pedido, copia o que cabe e troca a sua variável para apontar para o novo.
int[] notas = [7, 8, 6];
Array.Resize(ref notas, 5);
Console.WriteLine($"{notas.Length}: {string.Join(" ", notas)}");
Array.Resize(ref notas, 2);
Console.WriteLine($"{notas.Length}: {string.Join(" ", notas)}");
$ dotnet run crescer.cs 5: 7 8 6 0 0 2: 7 8 « diminuir corta o que sobra
- ref notas
- O
ref("referência") deixa o método trocar a sua variável por outra coisa: aqui, pelo array novo. Sem ele, o método não teria como mudar onotas(aula ref, out e in). - 5
- O novo tamanho. As posições novas recebem o valor padrão (0).
Como ele copia tudo a cada chamada, usar o Resize para acrescentar um elemento de cada vez, num laço, é lento. Se o tamanho muda o tempo todo, o certo é uma List<T>.
5. Achar por uma condição: Find e companhia
O IndexOf procura um valor exato. E para "a primeira nota abaixo de 7"? Aí você passa a condição, escrita como uma lambda: n => n < 7 se lê "n tal que n é menor que 7". O método testa a condição em cada elemento.
int[] notas = [7, 4, 9, 6, 8];
int primeira = Array.Find(notas, n => n < 7);
int posicao = Array.FindIndex(notas, n => n < 7);
int[] baixas = Array.FindAll(notas, n => n < 7);
bool algum10 = Array.Exists(notas, n => n == 10);
bool todasPositivas = Array.TrueForAll(notas, n => n > 0);
Console.WriteLine($"Primeira abaixo de 7: {primeira} (posição {posicao})");
Console.WriteLine($"Todas abaixo de 7: {string.Join(", ", baixas)}");
Console.WriteLine($"Alguém tirou 10? {algum10}");
Console.WriteLine($"Todas positivas? {todasPositivas}");
Console.WriteLine(Array.Find(notas, n => n > 10));
$ dotnet run achar.cs Primeira abaixo de 7: 4 (posição 1) Todas abaixo de 7: 4, 6 Alguém tirou 10? False Todas positivas? True 0 « nenhuma nota acima de 10: o Find devolveu o valor padrão
| Método | Devolve | Se ninguém passar na condição |
|---|---|---|
Array.Find(v, cond) | o primeiro elemento que passa | o valor padrão (0, null…) |
Array.FindLast(v, cond) | o último que passa | o valor padrão |
Array.FindIndex(v, cond) | a posição do primeiro | −1 |
Array.FindAll(v, cond) | um array novo com todos os que passam | um array vazio |
Array.Exists(v, cond) | true se pelo menos um passa | false |
Array.TrueForAll(v, cond) | true se todos passam | false (num array vazio, true) |
Cuidado com o Find em arrays de números: o 0 de "não achei" é igual a um 0 de verdade. Se o 0 pode estar no array, use o FindIndex e confira o −1. O LINQ tem métodos parecidos e ainda mais poderosos (Where, Any, All, First), na aula Filtrar e Projetar.
Quando dá errado
error CS0029: Não é possível converter implicitamente tipo "void" em "int[]"
$ cat sort-void.cs int[] notas = [7, 4, 9]; int[] ordenadas = Array.Sort(notas); $ dotnet run sort-void.cs /home/ana/cs/sort-void.cs(2,19): error CS0029: Não é possível converter implicitamente tipo "void" em "int[]" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: o Array.Sort não devolve nada (void, "vazio"): ele ordena o próprio array. Não há resultado para guardar. O mesmo vale para Reverse, Fill, Clear, Copy e Resize.
Como consertar: chame sozinho, numa linha (Array.Sort(notas);), e depois use o notas. Se quer manter o original, copie antes: int[] ordenadas = [.. notas]; Array.Sort(ordenadas);.
error CS1620: Argumento 1 não deve ser transmitido com a palavra-chave "ref"
$ cat resize-ref.cs int[] notas = [7, 8, 6]; Array.Resize(notas, 5); $ dotnet run resize-ref.cs /home/ana/cs/resize-ref.cs(2,14): error CS1620: Argumento 1 não deve ser transmitido com a palavra-chave "ref" Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: a tradução saiu trocada: o original em inglês é Argument 1 must be passed with the 'ref' keyword, "o argumento 1 tem de ser passado com a palavra-chave ref". Faltou o ref. (Mais um motivo para olhar o código do erro, CS1620, e pesquisar por ele.)
Como consertar: Array.Resize(ref notas, 5);.
Unhandled exception. System.ArgumentException: Destination array was not long enough.
$ cat copy-erro.cs int[] origem = [1, 2, 3, 4, 5]; int[] destino = new int[3]; Array.Copy(origem, destino, 5); $ dotnet run copy-erro.cs Unhandled exception. System.ArgumentException: Destination array was not long enough. Check the destination index, length, and the array's lower bounds. (Parameter 'destinationArray') at System.Array.CopyImpl(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable) at Program.<Main>$(String[] args) in /home/ana/cs/copy-erro.cs:line 3
O que significa: "o array de destino não é comprido o bastante": 5 elementos não cabem em 3 portinhas. A linha de cima da pilha (System.Array.CopyImpl) é de dentro do .NET; a que interessa é a última, com o seu arquivo e a linha 3. No editor do guia, a pilha mostra mais algumas linhas de dentro do .NET, mas a última é a mesma.
Como consertar: crie o destino com espaço suficiente (new int[origem.Length]), ou copie menos elementos.
$ cat original.cs
int[] chegada = [30, 12, 45, 7];
int[] ranking = chegada;
Array.Sort(ranking);
Console.WriteLine($"Ranking: {string.Join(", ", ranking)}");
Console.WriteLine($"Ordem de chegada: {string.Join(", ", chegada)}");
$ dotnet run original.cs
Ranking: 7, 12, 30, 45
Ordem de chegada: 7, 12, 30, 45 « a ordem de chegada se perdeu
O que significa: o ranking = chegada não copiou: as duas variáveis são o mesmo array (aula Arrays, seção 9).
Como consertar: int[] ranking = [.. chegada]; (ou (int[])chegada.Clone()).
BinarySearch disse que não achou, mas o valor está lá
O que significa: o array não estava em ordem. A busca binária confia na ordem para descartar metades, e descartou a metade errada.
Como consertar: Array.Sort antes, ou use Array.IndexOf, que funciona em qualquer ordem.
Referência rápida
| Método | O que faz | Devolve |
|---|---|---|
Array.Sort(v) | Ordena em ordem crescente (textos na ordem do idioma) | nada (muda v) |
Array.Sort(v, StringComparer.Ordinal) | Ordena textos pelos códigos das letras | nada |
Array.Sort(v, (a, b) => b.CompareTo(a)) | Ordem decrescente | nada |
Array.Sort(chaves, outros) | Ordena chaves e faz as mesmas trocas em outros | nada |
Array.Reverse(v) | Inverte a ordem | nada |
Array.IndexOf(v, x) / LastIndexOf | Posição da primeira / última ocorrência | índice, ou −1 |
v.Contains(x) | Está no array? (LINQ) | bool |
Array.BinarySearch(v, x) | Busca rápida num array ordenado | índice, ou negativo |
Array.Fill(v, x) | Põe x em todas as posições | nada |
Array.Clear(v) | Volta tudo ao valor padrão | nada |
Array.Copy(origem, destino, n) | Copia n elementos | nada |
(int[])v.Clone() / [.. v] | Cópia nova do array | o array novo |
Array.Resize(ref v, n) | Troca v por um array de tamanho n | nada |
Array.Find / FindIndex / FindAll | Primeiro / posição / todos que passam na condição | elemento / índice / array |
Array.Exists / TrueForAll | Algum passa? / Todos passam? | bool |
Exercícios
1. Ordenar
Complete para ordenar o array precos:
2. Não achou
O que Array.IndexOf(nomes, "Zeca") devolve quando "Zeca" não está no array?
3. Crescer
Complete para o array itens passar a ter 10 posições:
4. Depois de int[] a = [3, 1, 2]; Array.Sort(a); Array.Reverse(a);, como fica o a?
5. Quando o Array.BinarySearch dá um resultado confiável?
6. Qual linha dá erro de compilação?
Missão final: o pódio
Missão: os pontos dos jogadores estão na ordem em que eles jogaram. Use os métodos da classe Array para colocar do maior para o menor, e o laço já pronto mostra os três primeiros: 1º: 45, 2º: 40 e 3º: 30.
1º: 45 2º: 40 3º: 30
int[] pontos = [12, 45, 7, 30, 18, 40];
// coloque do maior para o menor aqui
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"{i + 1}º: {pontos[i]}");
}
int[] pontos = [12, 45, 7, 30, 18, 40];
Array.Sort(pontos);
Array.Reverse(pontos);
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"{i + 1}º: {pontos[i]}");
}
Com Sort, o array fica 7, 12, 18, 30, 40, 45; o Reverse vira para 45, 40, 30, 18, 12, 7, e as posições 0, 1 e 2 são o pódio. A solução com a lambda, Array.Sort(pontos, (a, b) => b.CompareTo(a));, também cumpre a missão.
Resumo
- A classe
Arrayé uma caixa de ferramentas para qualquer array: escrevaArray., o nome do método e o array entre parênteses. Array.Sortordena (textos na ordem do dicionário do pt-BR) eArray.Reverseinverte. Os dois mudam o array e não devolvem nada: para guardar o original, ordene uma cópia ([.. v]).Array.IndexOfprocura e devolve a posição, ou −1.Containsdiz só sim ou não.BinarySearché rápida, mas só em arrays ordenados.Fillenche,Clearzera,CopyeClonecopiam, eResize(ref v, n)troca o array por um de outro tamanho.Find,FindIndex,FindAll,ExistseTrueForAllrecebem uma condição, comon => n < 7.