Percorrendo e Processando Coleções

Percorrendo e Processando Coleções

Guardar dados é pouco; o valor está em extrair informação deles. A aula apresenta os quatro padrões que cobrem quase todo processamento de coleções — acumulação, contagem condicional, busca e maior ou menor — e mostra o esqueleto comum que os une e permite resolver casos novos.
Linguagem C#

• • 11 min de leitura

Você já sabe guardar muitos valores, em arrays e em listas. Mas uma coleção só ganha utilidade quando extraímos informação dela: qual é a soma de todas as notas? Quantos alunos foram aprovados? Qual é o maior valor? Existe um nome específico na lista? Essas perguntas se respondem percorrendo a coleção — visitando cada elemento — e processando os valores conforme os visitamos. Nesta aula, que fecha a Fase 4, você aprenderá os padrões clássicos de processamento de coleções: a soma, a contagem condicional, a busca, e a determinação de maior e menor. Não são conceitos novos — são combinações de laços (Fase 2) e coleções (esta fase) —, mas reuni-los em padrões reconhecíveis é o que transforma você de alguém que sabe as peças em alguém que sabe montá-las para resolver problemas reais.

Retomando o foreach em sua plenitude

Ao longo das últimas fases, encontramos o foreach algumas vezes, sempre prometendo que ele ganharia pleno sentido quando tivéssemos coleções de verdade. Chegou a hora. O foreach percorre qualquer coleção — array ou List — visitando cada elemento, sem que precisemos gerenciar índices ou tamanho:

List<int> notas = new() { 8, 6, 9, 7, 10 };

foreach (int nota in notas)   // para cada 'nota' em 'notas'...
{
    Console.WriteLine(nota);   // ...processa aquela nota.
}

O foreach é a forma preferida de percorrer uma coleção quando você só precisa visitar cada elemento, sem se importar com sua posição. Ele é mais limpo e mais seguro que o for: sem contador para inicializar, sem condição de parada para acertar, sem risco de acessar um índice fora dos limites. Você diz "para cada elemento, faça isto", e o foreach cuida de percorrer tudo, do primeiro ao último. Nos padrões que veremos a seguir, o foreach será nossa ferramenta principal; recorreremos ao for apenas quando a posição de fato importar.

Padrão 1: acumulação (soma, total)

O primeiro padrão fundamental é a acumulação: percorrer a coleção mantendo uma variável que acumula um resultado ao longo do caminho. O exemplo canônico é somar todos os valores:

List<int> notas = new() { 8, 6, 9, 7, 10 };
int soma = 0;   // o "acumulador", começa em zero (elemento neutro da soma)

foreach (int nota in notas)
{
    soma = soma + nota;   // a cada nota, acrescenta-a ao acumulador
}

Console.WriteLine($"Soma: {soma}");   // Soma: 40

A estrutura deste padrão é sempre a mesma, e vale reconhecê-la: uma variável acumuladora é criada antes do laço, com um valor inicial apropriado (zero para soma); dentro do laço, cada elemento é combinado com o acumulador; ao final, o acumulador contém o resultado. A partir da soma, a média é imediata — basta dividir pela quantidade de elementos (soma / notas.Count, com atenção à divisão inteira que vimos na Aula #06). O padrão de acumulação serve não só para somar, mas para qualquer resultado que se constrói item a item: um produto (acumulador inicia em 1), uma concatenação de textos (acumulador inicia em texto vazio), e mais.

Padrão 2: contagem condicional

Frequentemente queremos saber quantos elementos satisfazem uma certa condição — quantas notas são de aprovação, quantos preços estão acima de um limite. O padrão é uma acumulação especializada: um contador que só aumenta quando a condição é verdadeira:

List<int> notas = new() { 8, 4, 9, 5, 10, 3 };
int aprovados = 0;

foreach (int nota in notas)
{
    if (nota >= 6)        // se a nota é de aprovação...
    {
        aprovados = aprovados + 1;   // ...conta mais um aprovado
    }
}

Console.WriteLine($"Aprovados: {aprovados}");   // Aprovados: 3

Note a combinação de ferramentas: o foreach percorre, o if (Fase 2) decide se cada elemento conta, e o contador acumula. Este padrão — percorrer, testar uma condição, contar os que passam — resolve uma enorme variedade de perguntas do tipo "quantos...?". Trocando a condição do if, você conta quantos são pares, quantos começam com certa letra, quantos estão dentro de uma faixa. A estrutura permanece; só a condição muda.

Padrão 3: busca

Buscar é procurar um elemento específico ou verificar se ele existe. Para coleções, já vimos que a List oferece o método Contains, que faz isso prontamente — e, quando ele serve, é a melhor escolha. Mas é instrutivo entender o padrão de busca "manual", que se aplica a casos mais complexos (buscar um elemento que satisfaça uma condição, não apenas um valor exato):

List<string> nomes = new() { "Ana", "Beto", "Carla" };
bool encontrou = false;

foreach (string nome in nomes)
{
    if (nome == "Beto")
    {
        encontrou = true;
        break;   // achamos: não precisa continuar procurando (Aula #14)
    }
}

Console.WriteLine(encontrou ? "Encontrado!" : "Não encontrado.");

Dois pontos valem destaque. Primeiro, o break (Aula #14): assim que encontramos o que procurávamos, interrompemos o laço, pois continuar seria desperdício. Segundo, a última linha usa um recurso conciso, o operador condicional (condição ? valorSeVerdadeiro : valorSeFalso), que produz um de dois valores conforme a condição — aqui, "Encontrado!" se encontrou for verdadeiro, "Não encontrado." caso contrário. É uma forma compacta de um if/else que produz um valor; mencione-o como uma conveniência que você encontrará com frequência. (Para uma busca por valor exato numa List, lembre-se: nomes.Contains("Beto") faz o mesmo em uma linha; o padrão manual brilha quando a condição de busca é mais elaborada.)

Padrão 4: maior e menor

Encontrar o maior (ou o menor) valor de uma coleção é outro padrão clássico, e tem uma sutileza no valor inicial que vale entender. A ideia: percorrer mantendo uma variável com o "maior até agora", atualizando-a sempre que encontrar algo maior:

List<int> valores = new() { 23, 67, 12, 89, 45 };
int maior = valores[0];   // começa assumindo que o PRIMEIRO é o maior

foreach (int v in valores)
{
    if (v > maior)        // se encontrar um valor maior que o "maior até agora"...
    {
        maior = v;         // ...ele passa a ser o maior
    }
}

Console.WriteLine($"Maior valor: {maior}");   // Maior valor: 89

A sutileza está no valor inicial de maior: começamos com o primeiro elemento da coleção (valores[0]), e não com zero. Por quê? Porque zero seria um chute arbitrário — se todos os valores fossem negativos, o zero (que não está na coleção) seria erroneamente reportado como o maior. Iniciar com um elemento real da coleção garante que a resposta seja sempre um valor que de fato existe nela. Este é um cuidado importante do padrão maior/menor: o valor inicial deve ser um elemento genuíno, tipicamente o primeiro. (Para o menor, a lógica é idêntica, trocando > por <.) Vale mencionar que, para casos simples, a própria coleção oferece atalhos prontos — mas conhecer o padrão manual ensina o raciocínio e prepara você para variações que os atalhos não cobrem.

Uma visão de conjunto dos padrões

Recapitulando os quatro padrões que você agora domina: a acumulação constrói um resultado item a item (soma, produto, total); a contagem condicional conta quantos elementos satisfazem uma condição; a busca localiza um elemento ou verifica sua existência; e o maior/menor encontra os extremos. Todos compartilham a mesma espinha dorsal — percorrer a coleção com foreach, processando cada elemento — variando apenas o que se faz a cada passo. Reconhecer qual padrão um problema pede é meio caminho para resolvê-lo, e você os aplicará constantemente. Eles são as ferramentas com que transformamos coleções de meros depósitos de dados em fontes de respostas úteis.

Guardar uma coleção é pouco; o valor aparece ao extrair informação dela. Os quatro padrões vistos aqui cobrem a enorme maioria do que se faz com dados no dia a dia: acumular um total, contar quantos itens satisfazem um critério, localizar um item específico e descobrir o maior ou o menor.

O que eles têm em comum é mais importante do que suas diferenças. Todos declaram uma variável antes do laço, ajustam essa variável a cada item e usam o resultado depois que o percurso termina. Reconhecer essa forma recorrente transforma problemas aparentemente distintos em variações de um mesmo esqueleto, e é isso que permite resolver um caso novo sem procurar uma receita pronta.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Crie uma List<int> com sete números à sua escolha e use o padrão de acumulação para calcular e exibir a soma de todos eles. Depois, calcule e exiba a média, tomando cuidado com a divisão (converta para double se quiser casas decimais).

Ver resposta

✓ Resposta: Exemplo:

List<int> numeros = new() { 4, 8, 15, 16, 23, 42, 10 };
int soma = 0;
foreach (int n in numeros) soma += n;
Console.WriteLine($"Soma: {soma}");                          // 118
double media = (double)soma / numeros.Count;
Console.WriteLine($"Média: {media}");                        // 16.857...

A acumulação soma os sete valores; a média divide a soma pela quantidade (Count), com a conversão para double garantindo casas decimais.

Exercício 2

Crie uma List<int> com as idades de várias pessoas e use o padrão de contagem condicional para contar e exibir quantas são maiores de idade (18 ou mais). Depois, modifique para contar quantas são menores de idade, e verifique que as duas contagens somam o total.

Ver resposta

✓ Resposta: Exemplo:

List<int> idades = new() { 15, 22, 17, 30, 8, 19 };
int maiores = 0, menores = 0;
foreach (int idade in idades)
{
    if (idade >= 18) maiores++;
    else menores++;
}
Console.WriteLine($"Maiores: {maiores}, Menores: {menores}");   // Maiores: 3, Menores: 3

O padrão de contagem conta quantas idades satisfazem cada condição; a soma das duas contagens (3 + 3 = 6) iguala o total de elementos, confirmando que todos foram classificados.

Exercício 3

Crie uma List<string> com nomes e use o padrão de busca (com foreach e break) para verificar se um nome específico está presente, exibindo uma mensagem apropriada. Depois, reescreva a mesma verificação usando o método Contains e compare as duas abordagens.

Ver resposta

✓ Resposta: Com foreach e break:

List<string> nomes = new() { "Ana", "Beto", "Carla" };
bool achou = false;
foreach (string nome in nomes)
    if (nome == "Carla") { achou = true; break; }
Console.WriteLine(achou ? "Presente" : "Ausente");

Com Contains:

Console.WriteLine(nomes.Contains("Carla") ? "Presente" : "Ausente");

As duas produzem o mesmo resultado. O Contains é muito mais conciso e é a escolha preferível para busca por valor exato; o padrão manual com foreach/break é instrutivo e torna-se necessário quando a busca envolve uma condição mais elaborada que a simples igualdade.

Exercício 4

Crie uma List<int> com valores variados (incluindo alguns negativos) e use o padrão de maior/menor para encontrar e exibir o maior e o menor valor. Explique por que é importante iniciar as variáveis maior e menor com o primeiro elemento da lista, e não com zero.

Ver resposta

✓ Resposta: Exemplo:

List<int> valores = new() { -5, 12, -20, 8, 3 };
int maior = valores[0];
int menor = valores[0];
foreach (int v in valores)
{
    if (v > maior) maior = v;
    if (v < menor) menor = v;
}
Console.WriteLine($"Maior: {maior}, Menor: {menor}");   // Maior: 12, Menor: -20

É importante iniciar maior e menor com o primeiro elemento da lista porque zero é um valor arbitrário que pode não pertencer à coleção: se todos os valores fossem negativos (como parte deste exemplo), iniciar maior com zero reportaria zero como o maior, o que estaria errado, pois zero não está na lista. Iniciar com um elemento real (valores[0]) garante que o resultado seja sempre um valor que de fato existe na coleção.

Exercício 5

Sem código: os quatro padrões desta aula (acumulação, contagem condicional, busca, maior/menor) compartilham uma "espinha dorsal" comum. Descreva-a com suas palavras, e explique o que varia de um padrão para outro. Por que reconhecer o padrão que um problema pede facilita a solução?

Ver resposta

✓ Resposta: A espinha dorsal comum é: percorrer a coleção com um foreach, processando cada elemento à medida que é visitado, tipicamente atualizando uma variável mantida fora do laço. O que varia de um padrão para outro é o que se faz com cada elemento: na acumulação, combina-se o elemento a um acumulador (somando, por exemplo); na contagem condicional, testa-se uma condição e incrementa-se um contador quando ela é verdadeira; na busca, compara-se o elemento ao alvo e interrompe-se ao encontrá-lo; no maior/menor, compara-se o elemento ao extremo atual e atualiza-se quando apropriado. Reconhecer o padrão que um problema pede facilita a solução porque, identificado o padrão, a estrutura do código já está dada — resta apenas preencher a condição ou a operação específica —, o que transforma um problema aparentemente novo numa aplicação de uma receita já conhecida.

Comentários

Mais em Linguagem C#

Métodos: Dando Nome e Reúso às Ações
Métodos: Dando Nome e Reúso às Ações

O primeiro recurso real de organização do código: o método, que dá nome a uma…

A String a Fundo: Trabalhando com Texto
A String a Fundo: Trabalhando com Texto

Tudo o que se faz com texto em C#: medir com Length, compor mensagens por…

Um Blog Completo com ASP.NET Core
Um Blog Completo com ASP.NET Core

Um site navegável completo em C#: um blog com Razor Pages, com listagem de…