Primeiras aulas do curso Maratona de Programação: introdução e boas práticas de um programador maratonista

Maratona de Programação: introdução e boas práticas de um programador maratonista

O problema 42 - Introdução

Bem-vindos ao curso de Maratona de Programação! Neste curso focaremos em três problemas reais de sites distintos de maratonas de programação, e os resolveremos usando o Java. Portanto, a linguagem Java é um prerrequisito, mas é o único. Se você programa em outras linguagens e gostaria de ver esse curso mesmo assim, tudo bem. Se você sentir que seria melhor aprender Java antes, vá para os nossos cursos de Java e volte aqui depois.

A intenção não é apenas resolver os problemas de maratona correndo, com um código meia-boca. Nas competições, sejam elas regionais, nacionais ou mundiais, o desafio não é escrever um código que resolva o problema. Quando trabalhamos em uma equipe de programadores dentro de uma maratona mundial de programação, estamos interessados em resolver o problema de um cliente. Temos um problema a ser resolvido, um algoritmo simples, complexo ou muito complexo, com suas respectivas peculiaridades. O cliente apresenta esse problema, explicitando algumas coisas, mas há também detalhes implícitos que não conseguimos perceber tão facilmente. Por vezes há também algumas ambiguidades com as quais nós, programadores, precisamos saber lidar no cotidiano.

Enfrentamos todas essas questões em uma maratona de programação. Durante os 7 anos em que competi, sempre que precisava enviava uma submissão para mostrar que o meu programa estava resolvendo o problema, eu não podia correr o risco de o cliente dizer que o programa não funcionava em determinado caso. Isso tiraria pontos e eu perderia a chance de ir para o mundial representar o Brasil. Assim, quando vamos programar em uma maratona, e queremos realmente ir para campeonatos maiores, temos que pensar na qualidade do código. E essa qualidade deve ser entregue em diversos sentidos: o código precisa resolver todos os problemas do cliente, tudo o que ele pediu explicitamente e o que está implícito, o que mais vier junto com o problema, precisamos antecipar e resolver.

É igual ao nosso dia a dia de desenvolvedor de software: recebemos pedidos dos nossos clientes, com detalhes explícitos e implícitos, e temos que saber nos questionar se o algoritmo que estamos escrevendo, seja ele complexo ou simples, tem algum tipo de falha. E nas maratonas de programação, a margem de erro é 0. Se errarmos, perdemos pontos na lata. Assim como, na vida real, se entregarmos um projeto que não funciona para um cliente, ele reclamará com você.

Então a maratona e os exercícios de maratona devem te fornecer o mindset de "Eu preciso resolver, e preciso resolver bem de primeira", que é extremamente importante para o cotidiano do desenvolvedor. E é isso que eu quero passar aqui, até mais que os algoritmos em si. Muito mais do que aprender algoritmos interessantes, nesse curso você vai aprender a pensar com a cabeça de um programador sênior. A pensar como proteger o código para que o cliente tenha sua demanda atendida, usando exemplos de maratona de programação. São exemplos que envolvem algoritmos e ajudam a desenvolver a nossa cabeça e os nossos dedos na hora de desenvolver o código. E note que não falamos em qualidade de código no mínimo de caracteres possível. Em boa parte do mundo, a quantidade de código conta, o número de letras conta. E, de propósito, vamos trabalhar um exemplo em que isso faz diferença. No mundo real, você não está sendo pago para digitar menos. Você está sendo pago para resolver muito bem um problema. Na maratona de programação, a lógica é a mesma; você não está lá para escrever as variáveis a, b, c, d e e. Será verificado se a sua solução resolve mesmo o problema do cliente em todos os casos possíveis, com a qualidade suficiente. É dessa maneira que somos testados em uma maratona, e é dessa maneira que somos testados no mundo real.

Eu vou mostrar três problemas, e vamos resolvê-los com esse mindset devagar, vendo o passo a passo. Depois vou resolvê-los como eu fazia durante as maratonas, para mostra a você que realmente quer competir, como era quando eu participava de uma equipe. Hoje em dia, que já não posso participar por causa do limite de idade, eu trabalho com a mesma cabeça, querendo enviar o mínimo de bugs possível para o meu cliente. Então, o mindset aqui é o mais importante – e de quebra aprenderemos alguns algoritmos e a entender melhor o cliente. Depois desse curso, há muitos outros pelos quais você pode passar na Alura, por exemplo na parte de algoritmos, e para quem já viu alguns algoritmos, os de machine learning, para quem quiser ver temos também de estrutura de dados e de estatística. São as opções mais interessantes para depois que você completar esse curso. Então vamos começá-lo! Bons estudos!

O problema 42 - Problema 42: Pensando e atacando o problema

Vamos começar o primeiro problema. Ele está no site do Spoj, que é o Sphere Online Judge. O problema está nesse link.

SPOJ

É um problema bem básico, para que entendamos como funciona um juiz de código. Ele avaliará o comportamento do código, não a maneira que o código foi escrito. Vou focar, nessa aula e no curso, em mostrar que a maneira que pensamos durante a construção do código influencia no funcionamento do programa – inclusive se ele vai ou não funcionar. Criaremos alguns hábitos de programação saudáveis, que te ajudarão tanto nos programas da maratona quanto no dia a dia.

Vamos então para o problema em si. O seu enunciado é o seguinte:

TEST – life, the Universe, and Everything Your program is to use the brute-force approach in order to find the Answer to Life, the Universe, and Everything. More precisely... rewrite small numbers from input to output. Stop processing input after reading in the number 42. All numbers at input are integers of one or two digits.

Example:

Input:
1
2
88
42
99

Output:
1
2
88

Vamos analisar juntos? O título é "Teste – A vida, o Universo e Tudo mais". Às vezes os problemas têm uma pegada engraçada, com alguma brincadeira, e às vezes eles se aproximam mais da vida real. O enunciado diz que o seu programa deverá encontrar a resposta da vida, do universo e tudo mais. Geralmente a introdução do problema tem algum segredo em que precisamos prestar atenção. Por enquanto não vou prestar tanta atenção, e deixarei algumas coisas passarem de propósito. O enunciado continua, dizendo que devemos reescrever números pequenos da entrada para a saída, e deu um exemplo.

Repare que estou tomando cuidado para não falar tudo o que é relevante, para passarmos direto para o programa a ser feito e descubramos os erros que podem vir disso. Abriremos o Eclipse e faremos o código em Java. Lembrando que o foco do curso não é a linguagem de programação Java, mas a maneira de pensar e programar.

Se for a primeira vez que você abre o Eclipse, ele te pedirá o workspace. É bom que eu me lembra que defini o workspace como Users/guilherme/alura/Documents/workspace, para usar no terminal.

Workspace

Assim que ele abre o programa, devemos clicar em Workbench.

Workbench

Então, criaremos um novo projeto, clicando em File > New > Project.

File new project

Informaremos que o projeto será feiro em Java clicando em Java Project.

Java project

O nome do projeto será o nome do problema que estamos tentando resolver, já que o ideal é isolar cada problema em um projeto diferente. Assim, seu nome será test. Não por ser um teste nosso, mas porque o nome do problema é esse.

Project name

O Eclipse nos pergunta se queremos ir para uma perspectiva de Java, e aceitaremos.

Associated perspective

Ele abre um projeto para nós, com o diretório src. Nele, criaremos uma nova classe. Para seguir o padrão, ela se chamará Test. Não que isso esteja necessariamente certo.

New class

Teremos então o seguinte código:

package test;

public class Test {

}

Ele criou a classe dentro de um pacote chamado test, e não era isso que queríamos. Removi essa linha, pois quero o pacote padrão.

public class Test {

}

Note os pequenos x's vermelhos. O programa está reclamando que estamos trabalhando no diretório errado, pois, se a classe não tem pacote, deve estar na raiz.

Package

Podemos então mover para o diretório certo.

Package

Agora, colocaremos o método main para começar o programa.

public class Test {
  public static void main(String[] args){

  }

}

Precisamos que o programa leia números da minha entrada. Podemos pedir para que ele leia uma linha inteira de uma vez só, e passe para a seguinte. Para que ele leia uma linha da nossa entrada padrão, que é o teclado, podemos usar diversos recursos do Java. A maneira mais simples é usar um Scanner. Ao criá-lo, precisamos dizer de onde ele tirará suas leituras. Nesse caso, será de System.in. Usaremos o Command + Shift + O para importar.

import java.util.Scanner;

public class Test {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);

  }

}

Então, podemos pedir para o Scanner nos dar a próxima linha, usando nextLine(), que é um número.

import java.util.Scanner;

public class Test {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    int linha = scanner.nextLine();

  }

}

Entretanto, uma linha não é um int. É uma String. Portanto, temos que converter a string 1, no número 1. Seria um pouco ruim fazer isso toda vez. Felizmente, o Scanner é bonzinho, e tem o método nextInt. Então, se você sabe que o próximo trecho é um número inteiro, pode pedir para lê-lo com nextInt. Se houver dois números na mesma linha, separados com um espaço, ele pegará apenas o primeiro.

import java.util.Scanner;

public class Test {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    int numero = scanner.nextInt();

  }

}

Agora que lemos o número, podemos imprimi-lo, usando sysout.

import java.util.Scanner;

public class Test {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    int numero = scanner.nextInt();
    System.out.println(numero);

  }

}

Vamos rodar esse programa, que está lendo apenas um número? Basta clicar com o botão direito e em Run as > Java Application, ou Alt + Command + X + J.

Run as Java Application

E o que acontece? Nada. O programa está esperando você digitar alguma coisa. Precisamos ir no console, onde há entrada e saída. Clicaremos em Window > Show View > Console.

Show view

O console ficara na parte inferior do programa, e possui um botão de Stop, para que pare de rodar, se necessário.

Console stop

Nele, digitamos o número 56 e damos Enter. Em seguida, aparece novamente o número 56, que é o output.

56
56

E o programa para de rodar. O ideal é que ele continue a leitura por diversos número, sem parar. Para isso, precisamo de um laço, e o laço mais simples é o while(true).

import java.util.Scanner;

public class Test {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      int numero = scanner.nextInt();
      System.out.println(numero);

    }  

  }

}

Usamos o que conhecemos da linguagem para que ele rode eternamente e imprima os números.

Play

Ao rodar de novo, o programa espera que coloquemos um número no console. Colocaremos 1.

1
1

E ele prontamente nos devolve outro 1. Testaremos colocar outro número, para ver se ele continua funcionando.

1
1
2
2
56
56
22
22

Ele respondeu a todos os números, então parece que tudo está funcionando direitinho. Vamos parar o programa e já enviar para o juiz. Repare que estamos nos precipitando ao escolher enviar sem atentar a todos os detalhes que mostrarei em breve.

Para enviar, nos logaremos no Spoj. Eu já tenho a minha conta.

Sign in

Sign up

Caso você precise criar a sua, basta clicar em Sign up. Atualizaremos a tela do problema, para que ela perceba o nosso login e possamos submeter a resposta, clicando em Submit solution. Abriremos em uma aba nova com clicando com o Command pressionado.

Submit solution

O Spoj permite que você envie um arquivo ou cole o código diretamente no campo.

Submit solution

Vamos optar por colar o código. Deletaremos tudo o que está ali e substituiremos pelo que acabamos de escrever. Abaixo desse campo, devemos selecionar a linguagem (Java (JavaSE 8u51)) e clicar em Submit.

Submit

O site nos levará para outra tela, que nos mostra que o nosso código está rodando.

Carregando

E quando finalmente carrega, ele nos informa que a resposta está errada.

Wrong answer

O que pode estar errado? Qualquer coisa! Começaremos falando do laço, que vai rodando, independentemente do número inserido. Mas, em algum momento, o programa precisa parar. Vamos dar mais uma olhada no enunciado?

Your program is to use the brute-force approach in order to find the Answer to Life, the Universe, and Everything. More precisely... rewrite small numbers from input to output. Stop processing input after reading in the number 42. All numbers at input are integers of one or two digits.

Até a parte em que o enunciado pede para jogar os números da entrada para a saída nós lemos com atenção. Mas então nos apressamos em fazer o código, e ignoramos algumas especificações. Logo em seguida ele pede para que paremos de processar depois do número 42. Repare que também no exemplo isso é explicitado.

Input:
1
2
88
42
99

Output:
1
2
88

Nós que fomos preguiçosos e lemos pela metade. Repare que erramos duas vezes na leitura: não escutamos o cliente até o fim, e olhamos o exemplo por cima.

Vamos rodar nosso programa novamente, emulando o caso que o cliente especificou. Digitaremos no console os números do exemplo, observando sua resposta:

1
1
2
2
88
42
42

Veja que o nosso programa continuou rodando depois que digitamos o 42. Ou seja: erramos. Além disso, fica chato ficar digitando todas as vezes os mesmos números. O ideal é automatizar os testes, e é o que fazemos na prática. Em uma maratona de programação, não temos muito tempo de ficar criando muitos testes de unidade. Então fazemos um arquivo separado, na pasta src exclusiva para testes. Clicaremos com o botão direito na pasta e em New > File.

New File

O novo arquivo se chamará entrada1.txt. Nele, colocaremos um teste muito simples, até mais simples que o exemplo dado pelo cliente do problema.

1
2
89

Depois, vamos ao terminal e entraremos no diretório que estamos usando. Sabemos que temos o diretório test, e que dentro dele o Eclipse criou o diretório bin, onde há o text.class no qual está a nossa entrada1.txt.

Alura-Azul:~ alura$ cd guilherme/Documents/workspace
Alura-Azul:workspace alura$ ls
RemoteSystemsTempFiles test
Alura-Azul:test alura$ ls
bin src
Alura-Azul:test bin alura$ cd bin
Alura-Azul:bin alura$ ls
Test.class  entrada1.txt test

Agora pediremos para que o programa leia de entrada1.txt.

Alura-Azul:~ alura$ cd guilherme/Documents/workspace
Alura-Azul:workspace alura$ ls
RemoteSystemsTempFiles test
Alura-Azul:test alura$ ls
bin src
Alura-Azul:test bin alura$ cd bin
Alura-Azul:bin alura$ ls
Test.class  entrada1.txt test
Alura-Azul: bin alura$ java Test < entrada1.txt>

E o resultado é o seguinte:

Alura-Azul:~ alura$ cd guilherme/Documents/workspace
Alura-Azul:workspace alura$ ls
RemoteSystemsTempFiles test
Alura-Azul:test alura$ ls
bin src
Alura-Azul:test bin alura$ cd bin
Alura-Azul:bin alura$ ls
Test.class  entrada1.txt test
Alura-Azul: bin alura$ java Test < entrada1.txt>
1
2
89
Exception in thread "main" java.util.NoSuchElementException
        at java.util.Scanner.throwFor(Scanner.java:862)
        at java.util.Scanner.next(Scanner.java:1485)
        at java.util.Scanner.nextInt(Scanner.java:2117)
        at java.util.Scanner.nextInt(Scanner.java:2076)
        at Test.main(Test.java:7)

Deu erro! E com um exemplo ainda mais simples que o do problema. Isso quer dizer que os testes no Eclipse nos geram uma situação irreal. Não adianta rodar apenas no Eclipse, se o cliente rodar apenas no terminal. No nosso caso, sabemos que o juiz roda no terminal, então temos que rodar como ele.

Qual foi o problema do nosso programa? Ele chegou ao final do arquivo e continuou lendo. Ele deveria parar ao final do arquivo. No nosso teste ele leu o 89, imprimiu o 89 e procurou o próximo. Mas ele precisa parar se não houver mais nada para ler. Para isso, usaremos o hasNext. Se (if) não houver mais nada, o programa deve para (break).

import java.util.Scanner;

public class Test {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      int numero = scanner.nextInt();
      System.out.println(numero);
      if(!scanner.hasNext()) break;  

    }  

  }

}

Salvaremos e testaremos no terminal, depois de limpar sua tela.

Alura-Azul: bin alura$ java Test < entrada1.txt
1
2
89
Alura-Azul:bin alura$

Agora funcionou! Para esse exemplo, ao menos, que é um bom começo por ser mais simples do que o proposto no problema. Vamos testar com esse agora? Não podemos deixar que o cliente teste exatamente o que ele pediu e encarar um erro. Criaremos a entrada2.txt, da mesma forma que criamos a primeira. Nela, constará:

1
2
88
42
99

Agora, rodaremos esse segundo teste no terminal.

Alura-Azul: bin alura$ java Test < entrada1.txt
1
2
89
Alura-Azul:bin alura$ java Test < entrada2.txt
1
2
88
42
99

Não deu tão certo. Era para ele imprimir até o 88, pois deve parar no 42. Para corrigir isso, precisamos voltar ao código. Se (if) o número for 42, quero que o programa pare (break).

import java.util.Scanner;

public class Test {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      int numero = scanner.nextInt();
      if(numero == 42) break;

      System.out.println(numero);
      if(!scanner.hasNext()) break;  

    }  

  }

}

Repare que estamos cobrindo os dois ifs com dois testes: o entrada1.txt cobre o caso em que não há mais o que ler, e o entrada2.txt cobre o caso do 42.

Vamos testar novamente no terminal? Primeiro testaremos a entrada2.text

Alura-Azul: bin $alura java Test < entrada2.txt
1
2
88

Funcionou! Será que o primeiro exemplo ainda funciona?

Alura-Azul: bin alura$ java Test < entrada1.txt
1
2
89

Podemos tentar enviar esse código para o juiz. Copiaremos tudo e colaremos naquele campo de submissão. Depois de um tempo compilando, temos uma surpresa:

Compilation-error

Um erro de compilação! Vamos fechar essa tela e abrir novamente a tela de submissão. Quando olhamos com atenção, vemos que havia um o código antes de colarmos o nosso por cima. Ele é o seguinte:

import java.util.*;
import java.lang.*;

class Main
{
    public static void main (String[] args) throws java.lang.Exception
    {

    }
}

Repare no nome da classe que temos que enviar: Main. Não é Test, como havíamos presumido. Novamente, o programador não leu a especificação do cliente. Ele pediu que fosse no pacote padrão, pois não está escrito nada de package. Mas a classe deveria ser Main.

Assim, vamos renomear a classe no nosso código.

import java.util.Scanner;

public class Main {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      int numero = scanner.nextInt();
      if(numero == 42) break;

      System.out.println(numero);
      if(!scanner.hasNext()) break;  

    }  

  }

}

O programa vai pedir para renomear os arquivos, e vamos aceitar.

Rename type

Agora sim, podemos copiar tudo e colar sobre o código que lá estava. Quando submetemos, veremos o site nos responder com o seguinte:

Accepted

O código foi aceito! Conseguimos fazer de acordo com o que o cliente pediu. Até a próxima!

O problema 42 - Problema 42: Pensando na condição de parada

Para resolver o problema, tivemos que prestar atenção em diversos pontos. A começar pela leitura completa do enunciado, e saber que há situações que são explícitas – como escrito no enunciado "os números são inteiros". Se não estivesse assim, poderiam ser decimais também.

Será que o nosso programa funciona com decimais? Por exemplo, o 12,5. Vamos testar? Criaremos um novo arquivo, clicando com o botão direito no diretório e em New > File, chamado entrada3.txt para fazer esse teste.

12,5

Vamos agora para o terminal rodar esse teste.

Alura-Azul:bin alura$ java Test < entrada3.txt
Error: Could not find or load main class Test

Erramos o nome da classe, que agora é Main. Corrigindo:

Alura-Azul:bin alura$ java Main < entrada3.txt
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Scanner.java:846)
        at java.util.Scanner.next(Scanner.java:1485)
        at java.util.Scanner.nexInt(Scanner.java:2117)
        at java.util.Scanner.nextInt(Scanner.java:2076)
        at Main.main(Main.java:7)

Nós pedimos um int, mas temos um double, e por isso não funcionou. Mas, na verdade, isso não importa. Porque o nosso cliente especificou que eram números inteiros. Assim, não precisamos nos preocupar com entrada3.txt. Mas, se ele não falasse que era um número inteiro, poderiam ser números decimais. Mesmo que no exemplo dele só aparecessem números inteiros.

Se o cliente não especificar, fica aberto. Lembre-se que ele não usará todas as palavras possíveis para descrever todas as situações do mundo. Geralmente ele tentará explicar tudo, mas ficam alguns buracos. Nesse caso, não há dúvidas na questão do decimal; está explícito que é inteiro.

Mais uma questão: e se o número 42 se repetir? Criaremos a entrada4.txt para testar.

1
2
88
42
99
54
43
23
42
43
53
12

Temos dois números 42 aqui. Qual será o resultado disso? Vamos testar no terminal:

Alura-Azul: bin alura$ java Main < entrada4.txt
1
2
88

O resultado mostra só até o primeiro 42. Isso é um problema? Depende do que o cliente pediu. Como no pedido está escrito "Pare de processar o input depois de ler o número 42", não importa se há 42 novamente depois. Então não há problema, pois podemos assumir que o programa deve parar no primeiro 42 – independentemente de quantos 42 houver. E isso é corroborado pelo fato de o nosso programa ter sido aprovado no site.

Mas poderia ser que o cliente não queria dizer isso. Então cabe a nós, programadores, estarmos atentos a isso, às condições que o cliente nos dá. Será que é isso mesmo que ele quer dizer? Será que ele está pedindo para parar no primeiro 42 ou no último? Às vezes os clientes esquecem de falar algo para a gente, e precisamos confirmar com ele. Ele pode não tem considerado que poderia haver outro 42. Então, perguntamos para ele, porque, se for para parar no último, o código será totalmente diferente.

Temos que fazer esse tipo de questionamento e de teste sempre que lemos um novo problema. Nesse caso está tudo bem definido, mas veremos outro que tem coisas em aberto. Criaremos a entrada5.txt, e ele será um arquivo vazio.

Relendo o enunciado, não vemos nada sobre um arquivo vazio. Ele já presume que haverão números no arquivo. O que fazer se não há números? O que o nosso código faz se não tem números? Vamos para o terminal.

Alura-Azul: bin alura$ java Main < entrada5.txt
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Scanner.java:862)
        at java.util.Scanner.next(Scanner.java:1485)
        at java.util.Scanner.nexInt(Scanner.java:2117)
        at java.util.Scanner.nextInt(Scanner.java:2076)
        at Main.main(Main.java:7)

Deu erro! Será que era isso que o cliente esperaria? Será que esse teste é válido para o nosso cliente? Parece que sim, dado o enunciado. Mas o que ele esperava que fizéssemos? O programa deveria imprimir nada em vez de dar erro? Ou ele esperava esse erro mesmo?

Ao ler o pedido do cliente, o programador deve extrapolar todas as condições mencionadas pelo cliente. Quando ele diz "reescreva números pequenos da entrada para a saída", tínhamos que ter pensado "Quantos números?". Mais de um? Só um? Nenhum? Infinito? Não existe quantidade negativa, então isso não é um problema. De qualquer forma, temos que fazer essas questões para o cliente. Aparentemente o juiz do Spoj não considerou esse caso de arquivo vazio, até porque ele roda em um único arquivo. Pelas definições do enunciado, o cliente poderia ter tido esse problema, mas não deu instruções do que fazer no caso.

Nas maratonas de programação, se não está implícito nem explícito o que fazer nesses casos, é porque não será cobrado. Mas é preciso ter muita atenção para o caso de estar implícito, que aí precisamos perceber. Mas, na vida real, pode não estar implícito nem explícito, e você precisará perguntar para o cliente. Eu costumo fazer da seguinte maneira: se penso em um caso que o cliente não mencionou, implemento a solução mais simples possível, que exija o mínimo do meu código. E antes de implementar, mando um email perguntando a ele "Será que pode haver um caso de arquivo vazio?". Como eu não sei se ele vai responder o email em um minuto ou uma hora, faço a solução mais simples. Do contrário, posso acabar perdendo tempo.

Para encontrar a solução mais simples, vamos rever o código.

import java.util.Scanner;

public class Main {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      int numero = scanner.nextInt();
      if(numero == 42) break;

      System.out.println(numero);
      if(scanner.hasNext()) break;  

    }  

  }

}

Nós pedimos para o programa ler o próximo número inteiro. Podíamos primeiro pedir para ele verificar se existem números para ler, com a linha if(scanner.hasNext()) break;.

import java.util.Scanner;

public class Main {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      if(scanner.hasNext()) break;

      int numero = scanner.nextInt();
      if(numero == 42) break;

      System.out.println(numero);
      if(scanner.hasNext()) break;  

    }  

  }

}

Com essa decisão que tomamos, o programa não fará nada com o arquivo vazio. Vai perceber que ele é vazio e parar. Olhando novamente o código, vemos que está sujo. O começo e o fim do laço são a mesma linha. Podemos remover a última linha:

import java.util.Scanner;

public class Main {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      if(scanner.hasNext()) break;

      int numero = scanner.nextInt();
      if(numero == 42) break;

      System.out.println(numero);

    }  

  }

}

Vamos testar no terminal.

Alura-Azul: bin alura$ java Main < entrada5.txt

Não deu erro e não retorna nada: está funcionada. E os demais casos?

Alura-Azul: bin alura$ java Main < entrada5.txt
Alura-Azul: bin alura$ java Main < entrada4.txt
1
2
88
Alura-Azul: bin alura$ java Main < entrada3.txt
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Scanner.java:846)
        at java.util.Scanner.next(Scanner.java:1485)
        at java.util.Scanner.nexInt(Scanner.java:2117)
        at java.util.Scanner.nextInt(Scanner.java:2076)
        at Main.main(Main.java:9)

O caso 3 dá erro, mas não é um problema, porque o cliente explicitou que serão apenas números inteiros.

Alura-Azul: bin alura$ java Main < entrada5.txt
Alura-Azul: bin alura$ java Main < entrada4.txt
1
2
88
Alura-Azul: bin alura$ java Main < entrada3.txt
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Scanner.java:846)
        at java.util.Scanner.next(Scanner.java:1485)
        at java.util.Scanner.nexInt(Scanner.java:2117)
        at java.util.Scanner.nextInt(Scanner.java:2076)
        at Main.main(Main.java:9)
Alura-Azul: bin alura$ java Main < entrada2.txt
1
2
88
Alura-Azul: bin alura$ java Main < entrada1.txt
1
2
89

Todos os demais casos estão funcionando. Tem mais um detalhe que não gosto no código. Do jeito que o laço está escrito, existe uma possibilidade de não percebermos que ele é infinito e que o programa vai rodar para sempre. Um laço cuja condição é sempre verdadeira (true) exige um break no meio.

import java.util.Scanner;

public class Main {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(true) {
      if(scanner.hasNext()) break;

      int numero = scanner.nextInt();
      if(numero == 42) break;

      System.out.println(numero);

    }  

  }

}

Se não tiver nenhum break ou return no meio do laço, ele vai rodar eternamente, e podemos travar, por exemplo, uma thread do celular. Temos que tomar muito cuidado com o laço while(true), pois se escrevermos o break incorretamente, o programa ficará rodando. A regra geral é nunca escrever a palavra true em um laço, para não correr risco. Escrevemos o while associado à condição pertinente para o caso. Aqui, seria ter mais alguma coisa para ler, ou seja scanner.hasNext(), e com isso já podemos eliminar a linha do break.

import java.util.Scanner;

public class Main {
  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    while(scanner.hasNext()) {

      int numero = scanner.nextInt();
      if(numero == 42) break;

      System.out.println(numero);

    }  

  }

}

Se o Scanner tiver algo para ler, o laço continua. Do contrário, ele para. Jamais use o while(true). Pode haver um caso muito específico onde ele seja pertinente, mas em geral: se você escreveu um while(true), apague. É preciso tomar cuidado com a condição final de parada do laço. No nosso caso, colocamos uma condição tanto de parada quanto de início, que é a verificação de ter algo para ler. E a condição final também é ter o número 42.

Sempre que se cria um laço, é preciso parar para pensar. Se você começou a digitar while ou for, pare e pense:

  1. Qual a condição inicial? Do que eu tenho certeza quando esse laço começa?

  2. Qual é a condição final? O que precisa acontecer para esse laço terminar?

É preciso ter certeza de que você não está assumindo nada errado na condição inicial, como eu fiz, assumindo que haveria números em todos os arquivos. E também na condição final, que são duas: não ter mais nada para ler e o número 42. Esta estava escrita no enunciado, mas a primeira não. Essa tivemos que descobrir testando.

Com a solução desse problema pude mostrar diversas técnicas que podemos utilizar na criação de um laço e na leitura da entrada. São duas coisas que fazemos direto no dia a dia que exigem atenção às suas condições. Em breve resolveremos problemas mais difíceis. Até mais!

Sobre o curso Maratona de Programação: introdução e boas práticas de um programador maratonista

O curso Maratona de Programação: introdução e boas práticas de um programador maratonista possui 175 minutos de vídeos, em um total de 33 atividades. Gostou? Conheça nossos outros cursos de Computação em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Computação acessando integralmente esse e outros cursos, comece hoje!

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Conheça os Planos para Empresas

Acesso por 1 ano

Estude 24h/dia onde e quando quiser

Novos cursos todas as semanas