Aprenda Programação: Estruturas de Repetição (Laços ou Loops)
Créditos para a imagem: Imagem criada pelo autor usando o programa Spectacle.
Pré-Requisitos
Na introdução sobre ambientes de desenvolvimento, indiquei Python, Lua e JavaScript como boas escolhas de linguagens de programação para iniciantes. Posteriormente, comentei sobre GDScript como opção para pessoas que tenham interesse em programar jogos digitais ou simulações. Para as atividades de introdução a programação, você precisará de, no mínimo, um ambiente de desenvolvimento configurado em uma das linguagens anteriores.
Caso queira experimentar programação sem configurar um ambiente, você pode usar um dos editores online que criei:
Contudo, eles não possuem todos os recursos dos interpretadores para as linguagens. Assim, cedo ou tarde, você precisará configurar um ambiente de desenvolvimento. Caso precise configurar um, confira os recursos a seguir.
Assim, se você tem um Ambiente Integrado de Desenvolvimento (em inglês, Integrated Development Environment ou IDE) ou a combinação de editor de texto com interpretador, você está pronto para começar. Os exemplos assumem que você saiba executar código em sua linguagem escolhida, como apresentado nas páginas de configuração.
Caso queira usar outra linguagem, a introdução provê links para configuração de ambientes para as linguagens C, C++, Java, LISP, Prolog e SQL (com SQLite). Em muitas linguagens, basta seguir os modelos da seção de experimentação para adaptar sintaxe, comandos e funções dos trechos de código. C e C++ são exceções, por requerem o uso de ponteiros para acesso à memória.
Faça Uma Vez e Faça o Computador Repetir Várias
Existem problemas que requerem poucas entradas ou que analisam poucos objetos, seres, amostras ou entidades. Existem problemas que requerem o processamento e análise de dezenas, milhares, bilhões de entidades. Por exemplo, imagine que se deseje somar mil números, escrever uma mensagem mil vezes ou analisar um milhão de amostras. Embora seja possível duplicar código para realizar um mesmo processamento várias vezes, certamente não é algo recomendado nem prático.
O uso de subrotinas ameniza o problema. Ao invés de duplicar código necessário para processamento, define-se uma função ou um procedimento parametrizado para realizar a tarefa. Contudo, ainda é necessário duplicar chamadas de subrotinas. Para uma, duas, cinco entidades, o esforço é pequeno. Para centenas, milhares ou milhões, o esforço tornar-se-ia cada vez maior. Certamente seria possível replicar a chamada, mas existe uma forma melhor.
Computadores são máquinas excelentes para automação de tarefas. Ao invés de repetir instruções ou chamadas manualmente, o ideal é fazer com que o computador trabalhe por você. No cenário ideal, você resolve um problema uma única vez e instrui o computador para repetir a solução quantas vezes forem necessárias.
Na introdução sobre subrotinas, o uso de recursão permitiu criar código que se repetia, por meio de uma chamada que chamava ela mesma.
O uso de recursão para repetições é típico em programação no paradigma funcional, mas menos habitual no paradigma imperativo. No paradigma imperativo, é bastante comum usar estruturas de repetição (ou laços ou loops) para a mesma finalidade.
Estruturas de Repetição (Laços ou Loops)
Em livros, desenhos e filmes, existem castigos que requerem a escrita de uma mensagem múltiplas vezes.
Por exemplo, pode-se supor que seja necessário escrever Olá, meu nome é Franco!
cinco vezes.
Certamente seria possível escrever o código a seguir.
console.log("1. Olá, meu nome é Franco!")
console.log("2. Olá, meu nome é Franco!")
console.log("3. Olá, meu nome é Franco!")
console.log("4. Olá, meu nome é Franco!")
console.log("5. Olá, meu nome é Franco!")
print("1. Olá, meu nome é Franco!")
print("2. Olá, meu nome é Franco!")
print("3. Olá, meu nome é Franco!")
print("4. Olá, meu nome é Franco!")
print("5. Olá, meu nome é Franco!")
print("1. Olá, meu nome é Franco!")
print("2. Olá, meu nome é Franco!")
print("3. Olá, meu nome é Franco!")
print("4. Olá, meu nome é Franco!")
print("5. Olá, meu nome é Franco!")
extends Node
func _ready():
print("1. Olá, meu nome é Franco!")
print("2. Olá, meu nome é Franco!")
print("3. Olá, meu nome é Franco!")
print("4. Olá, meu nome é Franco!")
print("5. Olá, meu nome é Franco!")
Cinco vezes é um número pequeno. Para milhares de vezes, a tarefa seria mais cansativa.
Cada mensagem apresenta um mesmo padrão: escreva(contador, ". Olá, meu nome é Franco!")
.
Seria extramente conveniente definir um código como:
let contador = 1
console.log(contador, ". Olá, meu nome é Franco!")
++contador
contador = 1
print(contador, ". Olá, meu nome é Franco!")
contador += 1
local contador = 1
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
extends Node
func _ready():
var contador = 1
print(contador, ". Olá, meu nome é Franco!")
contador += 1
Em seqüência, seria conveniente instruir o computador a repetir a linha com o comando ou função de escrita da mensagem Cada repetição também pode ser chamada de iteração.
Combinando-se desvios e saltos, é possível criar código que se repete e realize o processamento arbitrário. A repetição pode ser infinita, caso não exista uma condição para determinar uma parada, ou, (preferencialmente) finita, quando combinada tal condição. Em outras palavras, uma forma de definir repetições em computadores é realizar um salto para uma região anterior do código-fonte de um programa.
Estruturas de repetição abstraem o salto e a verificação da condição por meio de algumas palavras reservadas como um comando. Com elas, é possível definir código que se repete zero ou mais vezes, ou uma ou mais vezes.
Os comandos de repetição mais comuns são:
- Com teste no início, para zero ou mais repetições:
enquanto
(while
) epara
(for
); - Com teste no fim, para uma ou mais repetições:
repita até
(repeat until
) erepita enquanto
(do while
).
Algumas linguagens oferecem comandos para ambos os casos; outras linguagens, apenas para um. Na prática, não se trata de uma limitação, pois é possível escrever código equivalente a qualquer estrutura usando qualquer outra estrutura.
Enquanto (while
)
O comando enquanto
é uma tradução direta do cenário descrito como combinação de salto e desvio.
Ele tem a seguinte estrutura como pseudocódigo:
enquanto (CONDICAO)
inicio
// Código que se repete.
// Código que potencialmente altera o resultado da condição.
fim
O comando enquanto
é um dos mais versáteis para repetições, por não impor limitações quanto ao uso de tipos e alteração de valores.
Ele analisa apenas uma expressão que deva resultar em um valor lógico.
Assim, CONDICAO
pode ser uma variável do tipo lógico, ou uma expressão relacional e/ou lógica.
Enquanto CONDICAO
resultar em Verdadeiro
, o código repete-se.
Isso significa que, ao invés de seguir após fim
, o programa volta à linha 1 (definição de enquanto
) para verificar novamente a condição.
Enquanto CONDICAO
continuar com o valor Verdadeiro
, o programa continuará a executar o bloco compreendido entre inicio
e fim
.
Quando CONDICAO
tiver resultado Falso
, o bloco finalmente terminará (no caso, avançando até a linha 6).
Caso CONDICAO
comece com resultado Falso
, o bloco será ignorado (o programa avançará até a linha 6 sem executar o bloco entre inicio
e fim
nenhuma vez).
Fluxogramas: Flowgorithm
Fluxogramas são um bom recurso para visualizar o funcionamento de repetições, especialmente caso se execute o programa passo a passo.
Para criar um bloco enquanto
em Flowgorithm, clique em uma seta para adicionar um novo bloco e escolha Enquanto (While).
Clique no novo bloco para definir a condição, que deve resultar em um valor lógico.
Para definir o código a ser repetido, adicione novos blocos na seta dentro do bloco.
O trecho de código a seguir contém a transcrição do texto da imagem.
Principal
Inteiro contador
contador = 1
Enquanto contador <= 5
Falso
Verdadeiro
Saída contador & "Olá, meu nome é Franco."
contador = contador + 1
Saída "Fim"
Fim
Pode-se notar que o bloco que será repetido está do lado contendo Verdadeiro
.
O lado com Falso
retorna ao fluxo normal de execução do programa, sem acrescentar nenhuma instrução.
Linguagens de Programação Visual: Scratch
Scratch define três blocos para repetições, disponíveis em Controle (Control): repita 10
vezes (repeat 10
), sempre (forever) e repita até que _
(repeat until _
).
Para incrementar contadores com maior facilidade, pode usar o bloco adicione 1
a minha variável
(change my variable
by 1
), que está disponível em Variáveis (Variables).
Scratch não define um bloco para enquanto
.
O mais próximo é repita até que _
(repeat until _
), cuja condição é a negação de enquanto
.
O bloco repita até que _
também é diferente de repita até
(repeat until
) em outras linguagens de programação, pois faz a verificação no início ao invés de no fim da estrutura.
Para evitar o uso de não
, pode-se usar a operação contrária.
A solução possivelmente será mais fácil de ler.
Linguagens de Programação Textual: JavaScript, Python, Lua e GDScript
Estruturas de repetição em linguagens de programação são, normalmente, definida em blocos. Algumas linguagens, como JavaScript, podem permitir a omissão de um bloco explícito para uma única linha, embora eu não recomende.
let contador = 1
while (contador <= 5) {
console.log(contador, ". Olá, meu nome é Franco!")
++contador
}
console.log("Fim")
contador = 1
while (contador <= 5):
print(contador, ". Olá, meu nome é Franco!")
contador += 1
print("Fim")
local contador = 1
while (contador <= 5) do
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
end
print("Fim")
extends Node
func _ready():
var contador = 1
while (contador <= 5):
print(contador, ". Olá, meu nome é Franco!")
contador += 1
print("Fim")
A mensagem escrita após o bloco de código apenas tem o propósito de adicionar uma saída após o término do laço, indicando o final da última repetição. Assim, a inclusão não é necessária para escrever programas.
Teste de Mesa (Trace Table ou Trace)
Para entender como o computador executa os programas anteriores, convém aprender uma técnica chamada teste de mesa (trace table ou simplesmente trace em Inglês). Em um teste de mesa, uma pessoa executa o código de um programa como se fosse um computador.
Uma forma de esquematizar um teste de mesa consiste em criar uma tabela que reúna a linha atual do código, as variáveis definidas no escopo, informações sobre entrada e saída (e/ou outros efeitos colaterais), e descrição de processamentos.
Como o código usado para o exemplo não possui entrada, a coluna será omitida a seguir. Para manter a numeração consistente, pode-se considerar a linhas para os blocos em JavaScript, Python e Lua. Embora o teste funcione da mesma forma para GDScript, a numeração estará atrasada de 3 linhas (ao invés de começar na linha 1, o código começará na linha 4).
Linha | contador | Saída | Descrição |
---|---|---|---|
0 | ? | Antes do início do programa | |
1 | 1 | Declaração e inicialização de contador | |
2 | 1 | 1 <= 5 é Verdadeiro ; o programa avança para a linha 3 | |
3 | 1 | 1. Olá, meu nome é Franco! | Saída de dados |
4 | 2 | Incremento de contador | |
2 | 2 | 2 <= 5 é Verdadeiro ; o programa avança para a linha 3 | |
3 | 2 | 2. Olá, meu nome é Franco! | Saída de dados |
4 | 3 | Incremento de contador | |
2 | 3 | 3 <= 5 é Verdadeiro ; o programa avança para a linha 3 | |
3 | 3 | 3. Olá, meu nome é Franco! | Saída de dados |
4 | 4 | Incremento de contador | |
2 | 4 | 4 <= 5 é Verdadeiro ; o programa avança para a linha 3 | |
3 | 4 | 4. Olá, meu nome é Franco! | Saída de dados |
4 | 5 | Incremento de contador | |
2 | 5 | 5 <= 5 é Verdadeiro ; o programa avança para a linha 3 | |
3 | 5 | 5. Olá, meu nome é Franco! | Saída de dados |
4 | 6 | Incremento de contador | |
2 | 6 | 6 <= 5 é Falso ; o programa avança para a linha 7 | |
7 | 6 | Fim | Última instrução do programa |
A segunda linha da tabela (a primeira com dados, ou seja, com valor 0 para Linha de código) serve apenas para ilustrar que valores de variáveis são indeterminados antes da declaração e inicialização. Após a declaração, o valor pode continuar indeterminado até a primeira atribuição (inicialização), ou a linguagem pode designar um valor padrão (embora linguagens comumente não façam isso).
Para entender a tabela, deve-se acompanhar o código definido além da tabela.
Começando da terceira linha da tabela (Linha 1 de código), cada programa em JavaScript, Python e Lua declara uma variável chamada contador
.
O valor atribuído para ela é 0
, configurando-se a inicialização.
Na seqüência, o programa avança para a próxima linha de código (Linha 2).
A linha 2 contém uma estrutura de repetição enquanto
.
A condição definida é contador <= 5
.
Como contador
possui, neste momento, valor 1
, a condição é 1 <= 5
que resulta em Verdadeiro
.
Assim, a próxima linha de código será a Linha 3, que escreverá uma mensagem (1. Olá, meu nome é Franco!
) e avançará para a Linha 4, com incremento de contador
.
Ao invés do programa seguir para a próxima linha fora da estrutura de repetição, ele realiza um salto de volta para a Linha 2.
Desta vez, contador
tem valor 2
.
Como 2 <= 5
, a condição resulta, novamente, em Verdadeiro
.
A próxima instrução, portanto, será a da Linha 3.
O bloco repete-se mais uma vez.
contador
assume valor 3
, que é menor ou igual a 5
.
Ou seja, nova repetição.
O bloco repete-se mais uma vez.
contador
assume valor 4
, que também é menor ou igual a 5
.
O bloco repete-se mais uma vez.
contador
assume valor 5
, que ainda é menor ou igual a 5
.
Neste incremento, contador
assumirá valor 6
.
Como 6 >= 5
resulta Falso
, a próxima linha de código será a Linha 7, que é a primeira linha com código após a estrutura de repetição.
Como a Linha 7 também a última linha do programa, ele escreve Fim
e termina.
Para fazer testes de mesa de forma digital, é possível usar um depurador (debugger). Depurador é uma ferramenta usada para inspecionar o funcionamento de um programa, normalmente com o intuito de ajudar a remover problemas existentes (bugs).
A Condição Determina o Número de Repetições
Caso você quisesse escrever a mensagem 100 vezes, o que você faria?
A reposta é simples: bastaria alterar a condição.
Ao invés de contador <= 5
, ela seria contador <= 100
.
Você pode modificar o código para observar o resultado.
Um computador escreverá as 100 mensagens rapidamente.
Caso você escolha um número maior (como 5000), talvez a execução demore alguns segundos.
Convém também pensar no que você faria para escrever a mensagem um número de vezes determinado pelo usuário (ou usuária) final. O que você faria?
Uma possibilidade é armazenar o número de repetições em uma variável (como ultimo_valor
, numero_vezes
ou repeticoes
) e usá-la na condição.
Além disso, é bastante comum definir repetições usando contadores inteiros cujo valor pertença ao intervalo ao invés de , sendo o contador e o número de repetições.
A convenção depende da linguagem de programação, normalmente correspondendo ao índices usados para vetores (arrays) na linguagem.
Assim, a numeração começaria em 0 para JavaScript, Python e GDScript (com condição contador < ultimo_valor
), mas em 1 para Lua (com condição contador <= ultimo_valor
).
Variáveis usadas para contagem são freqüentemente chamadas de contadores ou counters.
Também é bastante comum usar nomes i
, j
e k
para contadores, algo que também é comum em Matemática (além disso, i
é a primeira letra de inteiro ou integer).
Código que considere as melhores práticas e siga as convenções da uma linguagem é dito código idiomático.
Os próximos exemplos sintetizam as discussões desta subseção em exemplos. Os exemplos para JavaScript, Python e GDScript iniciam a contagem em zero. A condição de parada e a escrita do valor do contador na mensagem foram ajustados de acordo. O exemplo para Lua inicia a contagem em um. O exemplo para GDScript adota um número arbitrário de repetições, já que o motor Godot não fornece recursos para entrada de dados via console (terminal).
let contador = 0
let numero_repeticoes = parseInt(prompt("Número de repetições: "))
while (contador < numero_repeticoes) {
console.log(contador + 1, ". Olá, meu nome é Franco!")
++contador
}
contador = 0
numero_repeticoes = int(input("Número de repetições: "))
while (contador < numero_repeticoes):
print(contador + 1, ". Olá, meu nome é Franco!")
contador += 1
local contador = 1
print("Número de repetições: ")
local numero_repeticoes = io.read("*number")
while (contador <= numero_repeticoes) do
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
end
extends Node
func _ready():
var contador = 0
var numero_repeticoes = 5
while (contador < numero_repeticoes):
print(contador + 1, ". Olá, meu nome é Franco!")
contador += 1
Você pode optar pelo esquema de numeração que preferir. Entretanto, é bastante conveniente familiarizar-se com a numeração iniciada em zero, porque ela é bastante usual em programação. No próximos exemplos, a contagem em Lua normalmente começará em zero, por preferência pessoal do autor e para manter o padrão com os demais exemplos.
Verificação de Condição no Início
O comando enquanto
verifica a condição no início da estrutura.
Assim, é possível ignorar totalmente o bloco a ser repetido caso a condição inicial resulte em Falso
.
let contador = 5
while (contador < 5) {
console.log(contador, ". Olá, meu nome é Franco!")
}
console.log("Fim")
contador = 5
while (contador < 5):
print(contador, ". Olá, meu nome é Franco!")
print("Fim")
local contador = 5
while (contador < 5) do
print(contador, ". Olá, meu nome é Franco!")
end
print("Fim")
extends Node
func _ready():
var contador = 5
while (contador < 5):
print(contador, ". Olá, meu nome é Franco!")
print("Fim")
Em todos os blocos anteriores, a condição inicial é 5 < 5
, que resulta em Falso
.
Conseqüentemente, a execução do programa avança para a linha com a escrita de Fim
.
Laço (Loop) Infinito e Processos que Nunca Terminam
É importante notar que, caso a expressão usada como condição nunca se altere ou nunca resulte em Falso
, as repetições nunca terminarão.
Isso é chamado de laço infinito ou loop infinito.
Existem casos em que um programa deva repetir-se infinitamente.
Contudo, em geral, a ocorrência de um loop infinito decorre de erro de lógica durante o desenvolvimento do programa.
Todos os blocos de código a seguir repetem infinitamente, pois o valor de contador
jamais é alterado durante a repetição.
Assim, a condição contador < 5
será eternamente 0 < 5
, que sempre resultará Verdadeiro
.
let contador = 0
while (contador < 5) {
console.log(contador + 1, ". Olá, meu nome é Franco!")
}
console.log("Fim")
contador = 0
while (contador < 5):
print(contador + 1, ". Olá, meu nome é Franco!")
print("Fim")
local contador = 0
while (contador < 5) do
print(contador + 1, ". Olá, meu nome é Franco!")
end
print("Fim")
extends Node
func _ready():
var contador = 0
while (contador < 5):
print(contador + 1, ". Olá, meu nome é Franco!")
print("Fim")
Como a condição nunca muda, os códigos anteriores são equivalentes a enquanto (Verdadeiro)
.
while (true) {
console.log(contador, ". Olá, meu nome é Franco!")
}
console.log("Fim")
while (True):
print(contador, ". Olá, meu nome é Franco!")
print("Fim")
while (true) do
print(contador, ". Olá, meu nome é Franco!")
end
print("Fim")
extends Node
func _ready():
while (true):
print(contador, ". Olá, meu nome é Franco!")
print("Fim")
Cedo ou tarde, você escreverá um programa que estará (inadvertidamente) em loop infinito.
Para terminar o programa, você precisará encerrar o processo (também chamado de matar o processo).
Em interpretadores de comando, é comum que os atalhos Ctrl D
, Ctrl Z
, ou Ctrl C
(embora Ctrl C
possa deixar o programa rodando em segundo plano, algo que nem sempre é desejável) terminem processos.
Em IDEs, normalmente pode-se usar um ícone como stop
(⏹
).
Outra possibilidade é usar o gerenciador de processos do sistema para encerrar o processo.
Em Linux, isso pode ser feito como combinação dos comandos ps aux | grep NOME_PROCESSO
para identificar o identificador do processo (process ID ou PID), seguido de kill NUMERO_PID
.
Caso o processo não termine, é possível forçar o término usando-se kill -9 NUMERO_PID
.
Outra alternativa é usar o comando killall
, que encerra processos por nome (por exemplo, killall NOME_PROCESSO
).
Em ambientes gráficos, também é possível usar gerenciadores.
Por exemplo, no KDE, pode-se usar o atalho Ctrl Esc
para abrir o monitor com atividades do sistema.
Ao escolher um processo na lista, é possível usar um botão para encerrá-lo.
No Windows, pode-se usar o atalho Ctrl Alt Delete
(e escolher a opção Gerenciador de Tarefas ou Task Manager) ou Ctrl Shift Esc
para iniciar o gerenciador de processos (Task Manager).
Em seguida, deve-se acessar a aba Processos (Processes), escolher o nome do processo da lista, selecioná-lo e usar a opção Finalizar Tarefa (End Task).
Omissão de Chaves para Blocos
Assim como ocorre para estruturas condicionais, em linguagens como C, C++ e JavaScript, é possível omitir o uso de chaves para a definição de um bloco caso se queira repetir uma única linha de código.
let contador = 0
while (contador < 5) {
console.log(contador + 1, ". Olá, meu nome é Franco!")
++contador
}
contador = 0
while (contador < 5)
// Incremento feito aqui, para evitar loop infinito.
console.log(++contador, ". Olá, meu nome é Franco!")
contador = 0
while (contador++ < 5)
console.log(contador, ". Olá, meu nome é Franco!")
console.log("Fim")
Pelos mesmos motivos apresentados em estruturas de condição, eu prefiro sempre definir blocos explicitamente com chaves.
Para (for
)
Uma estrutura de repetição como enquanto
pode ser sumarizada em quatro partes:
- A inicialização da(s) variável(is) usada(s) como condição. Em particular, é bastante comum que a variável para a condição seja definida como um contador ou outra variável do tipo inteiro;
- A verificação da condição;
- O bloco de código para se repetir;
- A alteração da(s) variável(is) usada(s) como condição.
Muitas linguagens de programação oferecem uma estrutura de repetição chamada de para
, que combina em uma única linha a declaração (e/ou inicialização) de variável para condição, alteração de valor para a variável e verificação da condição definida.
A estrutura tem pseudocódigo similar a:
para VARIAVEL de VALOR_INICIAL ate VALOR_FINAL de VALOR_INCREMENTO
inicio
// Código que se repete.
fim
A estrutura anterior é comum em linguagens de programação que definem para
para tipos inteiros ou numéricos.
A estrutura é equivalente ao seguinte comando enquanto
:
VARIAVEL = VALOR_INICIAL
enquanto (VARIAVEL < VALOR_FINAL)
// ou VARIAVEL <= VALOR_FINAL, dependendo da linguagem
inicio
// Código que se repete.
VARIAVEL += VALOR_INCREMENTO
fim
Algumas linguagens de programação definem a estrutura de forma um pouco diferente e genérica, permitindo o uso com outros tipos de dados:
para VARIAVEL de INICIALIZACAO enquanto CONDICAO modificador por ATUALIZACAO
inicio
// Código que se repete.
fim
Nesse caso, a estrutura é equivalente ao seguinte comando enquanto
:
VARIAVEL = INICIALIZACAO
enquanto (CONDICAO)
inicio
// Código que se repete.
// ATUALIZACAO normalmente usa VARIAVEL de alguma forma.
VARIAVEL = ...
// Mudança de resultado ou de valor lógico para CONDICAO.
// CONDICAO = ...
fim
O uso do comando para
permite definir repetições de forma bastante sucinta.
Além disso, como declaração, comparação e incremento estão agrupados em uma mesma linha, torna-se mais difícil esquecer-se se realizá-las, potencialmente evitando-se erros.
Fluxogramas: Flowgorithm
Para usar o comando para
em Flowgorithm, escolha Para (For) após clicar em uma seta.
Na definição do bloco, será necessário escolher o nome de uma variável, o valor inicial, o valor final, o sinal do incremento (crescente ou increasing para incremento, decrescente ou decreasing para decremento) e o passo (valor do incremento/decremento).
O trecho de código a seguir contém a transcrição do texto da imagem.
Principal
Inteiro contador
Para contador = 0 até 4
Pronto
Próximo
Saída (contador + 1) & "Olá, meu nome é Franco."
Saída "Fim"
Fim
As alternativas definidas são Próximo (Next) e Pronto (Done). Deve-se observar que o valor final é incluído nas repetições, assim como ocorrerá na estrutura para Lua.
Linguagens de Programação Visual: Scratch
Em Scratch, repita 10
vezes (repeat 10
) é o bloco mais próximo de para
.
Para usá-lo, basta fornecer um número ou uma variável que armazene o número de vezes que se deseja repetir o código definido no bloco.
Na imagem, poder-se-ia substituir o valor 5
por uma variável com um número de repetições.
Alternativamente, no caso da imagem em particular, seria possível omitir o uso da variável caso não se desejasse escrever o número do contador na frase.
Linguagens de Programação Textual: JavaScript, Python, Lua e GDScript
Como comentado, os recursos de para
podem variar entre linguagens de programação.
for (let contador = 0; contador < 5; ++contador) {
console.log(contador + 1, ". Olá, meu nome é Franco!")
}
console.log("Fim")
for contador in range(0, 5, 1):
print(contador + 1, ". Olá, meu nome é Franco!")
print("Fim")
for contador = 0, 4, 1 do
print(contador + 1, ". Olá, meu nome é Franco!")
end
print("Fim")
extends Node
func _ready():
for contador in range(0, 5, 1):
print(contador + 1, ". Olá, meu nome é Franco!")
print("Fim")
Em todas as linguagens, a declaração da variável para a repetição, a verificação da condição e atualização do valor foram definidas em uma única linha de código.
Em JavaScript, o formato é:
for (VARIAVEL; CONDICAO; ATUALIZACAO)
. Na linguagem, o comandopara
é semelhante ao comandoenquanto
, mas pode ser escrito de forma mais conveniente. A variável criada como contador é uma variável local à estrutura, caso seja definida usandolet
;Em Python, o formato é:
for VARIAVEL in range(VALOR_INICIAL, VALOR_FINAL, INCREMENTO)
. Deve-se notar que a condição usada por padrão é:VALOR_INICIAL < VALOR_FINAL
, seINCREMENTO > 0
;VALOR_INICIAL > VALOR_FINAL
, seINCREMENTO < 0
.
Além disso, é possível omitir alguns campos em
range()
(documentação). Existem três formas principais de uso:range(VALOR_INICIAL, VALOR_FINAL, INCREMENTO)
;range(VALOR_INICIAL, VALOR_FINAL)
: assume queINCREMENTO
seja 1;range(VALOR_FINAL)
: assume queVALOR_INICIAL
seja 0 eINCREMENTO
seja 1.
A variável criada como contador é uma variável local à estrutura.
Em Lua, o formato é:
for VARIAVEL = VALOR_INICIAL, VALOR_FINAL, INCREMENTO do
.Deve-se observar que a condição usada por padrão é:
VALOR_INICIAL <= VALOR_FINAL
, seINCREMENTO > 0
;VALOR_INICIAL >= VALOR_FINAL
, seINCREMENTO < 0
.
Assim, em Lua, o valor final está incluído na condição. Isso é conveniente porque, na linguagem, é mais usual começar contagens por 1 ao invés de 0. Assim, pode-se usar os valores de
1
atéVALOR_FINAL
, ao invés de0
atéVALOR_FINAL - 1
.Assim como em Python, é possível omitir o último campo:
for VARIAVEL = VALOR_INICIAL, VALOR_FINAL, INCREMENTO do
;for VARIAVEL = VALOR_INICIAL, VALOR_FINAL do
: assume queINCREMENTO
seja 1.
A variável criada como contador é uma variável local à estrutura.
Em GDScript, o formato é o mesmo usado em Python.
Os próximos blocos de código ilustram casos de contagem regressiva.
for (let contador = 5; contador > 0; --contador) {
console.log(contador, ". Olá, meu nome é Franco!")
}
console.log("Fim")
for contador in range(5, 0, -1):
print(contador, ". Olá, meu nome é Franco!")
print("Fim")
for contador = 5, 1, -1 do
print(contador, ". Olá, meu nome é Franco!")
end
print("Fim")
extends Node
func _ready():
for contador in range(5, 0, -1):
print(contador, ". Olá, meu nome é Franco!")
print("Fim")
Os próximos blocos ilustram um exemplo de zero repetições.
for (let contador = 5; contador < 5; ++contador) {
console.log(contador + 1, ". Olá, meu nome é Franco!")
}
console.log("Fim")
for contador in range(5, 5, 1):
print(contador + 1, ". Olá, meu nome é Franco!")
print("Fim")
for contador = 5, 4, 1 do
print(contador + 1, ". Olá, meu nome é Franco!")
end
print("Fim")
extends Node
func _ready():
for contador in range(5, 5, 1):
print(contador + 1, ". Olá, meu nome é Franco!")
print("Fim")
Em linguagens de programação como C, C++ e JavaScript, todos os campos do comando para
são opcionais.
Assim, é possível fazer qualquer combinação com os campos.
Os exemplos a seguir são válidos, embora eu não recomendaria o uso:
let i = 0
// Equivalente a while (i < 3).
for (; i < 3; ) {
++i
}
// Equivalente a while (j < 3).
for (let j = 0; ;) {
++j
if (j >= 3) {
break
}
}
// Equivalente a while (k < 3).
let k = 0
for (; ; ++k) {
if (k >= 3) {
break
}
}
// Equivalente a while (l < 3).
for (let l; l < 3; ) {
++l
}
// Loop infinito: equivalente a while (true).
for (;;) {
}
Quando se está aprendendo a programar, é interessante evitar o uso de comandos como interrompa
(break
).
Existem pessoas que, inclusive, proíbem o uso de comandos como interrompa
em atividades didáticas ou profissionais.
Pessoalmente, eu considero um recurso como qualquer outro.
Caso o resultado seja mais simples de ler (ou, no caso de otimizações, mais eficaz), pessoalmente não considero o uso um problema.
Entretanto, para repetições, o uso de enquanto
é muito mais claro que um uso de para
adaptado com interrompa
ou com omissão de campos.
Assim, exceto para apresentar a curiosidade, há poucas razões para usar interrompa
em conjunto com para
.
Normalmente é melhor e mais simples optar por enquanto
.
Repita Até (repeat... until
) e/ou Repita Enquanto (do... while
)
Algumas linguagens de programação fornecem estruturas de repetição com verificação no fim, que sempre executam o código do bloco ao menos uma vez.
Existem duas estruturas comuns para tal fim: repita até
(repeat until
) e repita enquanto
(do while
).
Algumas linguagens fornecem uma das duas; outras fornecem ambas; algumas não definem nenhuma delas.
Em pseudocódigo, ambas as estruturas são similares. Repita até:
repita
inicio
// Código que se repete.
// Código que potencialmente altera o resultado da condição.
ate (CONDICAO)
Repita enquanto:
repita
inicio
// Código que se repete.
// Código que potencialmente altera o resultado da condição.
enquanto (CONDICAO)
A diferença entre elas é que repita até
repete um bloco de código enquanto a condição resultar em Falso
(ou seja, até que a condição seja resulte em Verdadeiro
), ao passo que repita enquanto
repete o código enquanto a condição resultar em Verdadeiro
.
Uma forma de pensar na diferença é que até
equivalente a não CONDICAO_ENQUANTO
.
Fluxogramas: Flowgorithm
Para usar o comando repita enquanto
em Flowgorithm, escolha Fazer (Do) após clicar em uma seta.
A definição da condição para o bloco é similar ao bloco Enquanto (While).
O trecho de código a seguir contém a transcrição do texto da imagem.
Principal
Inteiro contador
contador = 1
// Início Fazer
Falso
Verdadeiro
Saída contador & "Olá, meu nome é Franco."
contador = contador + 1
Fazer contador <= 5
Saída "Fim"
Fim
As alternativas disponíveis são Verdadeiro (_True) e Falso (False). As repetições ocorrem para o caso Verdadeiro; o caso Falso retorna ao fluxo principal.
Linguagens de Programação Textual: JavaScript, Python, Lua e GDScript
Python e GDScript não fornecem nenhuma das estruturas.
JavaScript define repita enquanto
.
Lua define repita até
.
let contador = 1
do {
console.log(contador, ". Olá, meu nome é Franco!")
++contador
} while (contador <= 5)
console.log("Fim")
local contador = 1
repeat
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
until (contador > 5)
print("Fim")
Nos dois blocos de código, pode-se observar que as condições são opostas.
Em JavaScript, a condição para repetição é contador <= 5
; em Lua, a condição é contador > 5
.
Assim, a condição para uma é a negação da outra.
Em linguagens que não definem nenhuma das duas estrutura, é simples simulá-la.
Uma forma é duplicar o código para a primeira repetição.
Por exemplo, os próximos blocos simulam uma estrutura repita enquanto
usando o enquanto
convencional.
let contador = 1
console.log(contador, ". Olá, meu nome é Franco!")
++contador
while (contador <= 5) {
console.log(contador, ". Olá, meu nome é Franco!")
++contador
}
console.log("Fim")
contador = 1
print(contador, ". Olá, meu nome é Franco!")
contador += 1
while (contador <= 5):
print(contador, ". Olá, meu nome é Franco!")
contador += 1
print("Fim")
local contador = 1
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
while (contador <= 5) do
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
end
print("Fim")
extends Node
func _ready():
var contador = 1
print(contador, ". Olá, meu nome é Franco!")
contador += 1
while (contador <= 5):
print(contador, ". Olá, meu nome é Franco!")
contador += 1
print("Fim")
A alternativa funciona porque o código fora da estrutura de repetição sempre será executado. Conseqüentemente, o código será executado, no mínimo, uma vez.
Para evitar a duplicação de código, é possível extrair o código comum para uma subrotina.
function codigo_a_repetir(contador) {
console.log(contador, ". Olá, meu nome é Franco!")
return contador + 1
}
let contador = 1
contador = codigo_a_repetir(contador)
while (contador <= 5) {
contador = codigo_a_repetir(contador)
}
console.log("Fim")
def codigo_a_repetir(contador):
print(contador, ". Olá, meu nome é Franco!")
return contador + 1
contador = 1
contador = codigo_a_repetir(contador)
while (contador <= 5):
contador = codigo_a_repetir(contador)
print("Fim")
function codigo_a_repetir(contador)
print(contador, ". Olá, meu nome é Franco!")
return contador + 1
end
local contador = 1
contador = codigo_a_repetir(contador)
while (contador <= 5) do
contador = codigo_a_repetir(contador)
end
print("Fim")
extends Node
func codigo_a_repetir(contador):
print(contador, ". Olá, meu nome é Franco!")
return contador + 1
func _ready():
var contador = 1
contador = codigo_a_repetir(contador)
while (contador <= 5):
contador = codigo_a_repetir(contador)
print("Fim")
Com a função, toda mudança feita em codigo_a_repetir()
aplicar-se-ia à primeira e a todas as eventuais outras repetições.
Outra possibilidade seria usar interrompa
.
Os próximos exemplos implementam repita até
usando enquanto
e interrompa
.
let contador = 1
while (true) {
console.log(contador, ". Olá, meu nome é Franco!")
++contador
if (contador > 5) {
break
}
}
console.log("Fim")
contador = 1
while (True):
print(contador, ". Olá, meu nome é Franco!")
contador += 1
if (contador > 5):
break
print("Fim")
local contador = 1
while (true) do
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
if (contador > 5) then
break
end
end
print("Fim")
extends Node
func _ready():
var contador = 1
while (true):
print(contador, ". Olá, meu nome é Franco!")
contador += 1
if (contador > 5):
break
print("Fim")
Caso se queira evitar o uso de interrompa
, pode-se usar uma expressão lógica.
let contador = 1
let primeira_repeticao = true
while ((primeira_repeticao) || (contador <= 5)) {
primeira_repeticao = false
console.log(contador, ". Olá, meu nome é Franco!")
++contador
}
console.log("Fim")
contador = 1
primeira_repeticao = True
while ((primeira_repeticao) or (contador <= 5)):
primeira_repeticao = False
print(contador, ". Olá, meu nome é Franco!")
contador += 1
print("Fim")
local contador = 1
local primeira_repeticao = true
while ((primeira_repeticao) or (contador <= 5)) do
primeira_repeticao = false
print(contador, ". Olá, meu nome é Franco!")
contador = contador + 1
end
print("Fim")
extends Node
func _ready():
var contador = 1
var primeira_repeticao = true
while ((primeira_repeticao) or (contador <= 5)):
primeira_repeticao = false
print(contador, ". Olá, meu nome é Franco!")
contador += 1
print("Fim")
O uso de ou
força que a primeira repetição sempre ocorra, usando uma dominação, já que primeira_repeticao
sempre será True
antes das repetições.
Ou seja, na primeira comparação, a expressão é equivalente à enquanto (primeira_repeticao)
, ou seja, enquanto (Verdadeiro)
.
Na primeira iteração dentro da estrutura, primeira_repeticao
transforma-se em Falso
.
Pela identidade, a condição passará a ser equivalente a enquanto (contador <= 5)
da segunda iteração em diante.
O resultado é, pois, uma implementação equivalente ao comando repita enquanto
.
Para mais uma alternativa de implementação, pode-se definir um valor inicial que force a ocorrência da primeira iteração em um enquanto
, corrigindo-se o valor dentro do bloco a ser repetido.
Embora menos genérica que as anteriores, ela é possível caso de conheça os valores e limites de antemão.
Um exemplo de tal aplicação é a construção de blocos com um número variável de repetições, que será apresentado nas técnicas a seguir.
Técnicas e Conceitos Adicionais
Conforme se adquire experiência em programação, é comum identificar técnicas e soluções recorrentes para problemas semelhantes (por exemplo, padrões de software). Esta seção apresenta alguns tópicos adicionais sobre estruturas de repetição, para que você tenha novas idéias e novos recursos para usar em seus projetos.
Número Variável de Repetições
Como apresentado anteriormente, é possível definir um número em uma variável como limite superior para a parada de um laço. Em potencial, ela pode ser lida como entrada para se definir um número fixo de repetições.
Também existe outra possibilidade. Ao invés de fixar-se um número de repetições, pode-se solicitar, continuamente, a entrada de um valor que demarque o fim de uma repetição. O valor pode ser de qualquer tipo: um número, uma cadeia de caracteres ou um valor lógico. Para determinar se o laço deve terminar, basta comparar o valor lido com um valor esperado.
Os programas a seguir terminam apenas quando se escreve fim
(com qualquer combinações de letras maiúsculas ou minúsculas) como entrada.
O exemplo para GDScript adota um número fixo de tentativas para alterar o valor da variável.
let texto = ""
let contador = 0
while (texto.toLowerCase() !== "fim") {
texto = prompt("Escreva fim para terminar as repetições.")
++contador
}
console.log("Você forneceu ", contador, " entrada(s).")
texto = ""
contador = 0
while (texto.lower() != "fim"):
texto = input("Escreva fim para terminar as repetições. ")
contador += 1
print("Você forneceu ", contador, " entrada(s).")
local texto = ""
local contador = 0
while (texto:lower() ~= "fim") do
print("Escreva fim para terminar as repetições. ")
texto = io.read("*line")
contador = contador + 1
end
print("Você forneceu ", contador, " entrada(s).")
extends Node
func _ready():
var texto = ""
var contador = 0
var numero_repeticoes = 5
while (texto.to_lower() != "fim"):
contador += 1
if (contador == numero_repeticoes):
texto = "FIM"
print("Você forneceu ", contador, " entrada(s).")
Ao invés de fim
, poder-se-ia usar um número específico, ou uma condição esperada como combinação de operadores relacionais e lógicos.
Assim, pode-se adaptar a técnica conforme os requisitos de um problema.
Validação de Entrada
Com estruturas condicionais, era possível verificar se uma entrada era válida ou inválida. Com estruturas de repetição, agora é possível reler valores até a entrada de um valor considerado válido.
O exemplo a seguir solicita a entrada de um valor inteiro, repetindo a leitura caso não se forneça um.
let numero = parseInt(prompt("Digite um número: "))
while (isNaN(numero)) {
console.log("O valor fornecido não é um número.")
numero = parseInt(prompt("Digite um número: "))
}
console.log("2 * ", numero, " = ", 2 * numero)
numero = None
while (numero == None):
try:
# int() / float()
numero = int(input("Digite um número: "))
except ValueError:
print("O valor fornecido não é um número.")
print("2 * ", numero, " = ", 2 * numero)
print("Digite um número: ")
local numero = tonumber(io.read("*line"))
while (numero == nil) do
print("O valor fornecido não é um número.")
print("Digite um número: ")
numero = tonumber(io.read("*line"))
end
print("2 * ", numero, " = ", 2 * numero)
extends Node
func _ready():
# Número definido no código, pois GDScript não permite leitura via terminal.
var valor = "Franco"
var contador = 0
# is_valid_float() / is_valid_integer()
while (not valor.is_valid_integer()):
print("O valor fornecido não é um número.")
if (contador < 3):
contador += 1
else:
valor = "1234"
var numero = int(valor)
print("2 * ", numero, " = ", 2 * numero)
A validação de entrada é um bom exemplo de problema que pode ser resolvido com estrutura de repetição com teste no fim.
Tente modificar a solução usando repita enquanto
, repita até
ou simulando a repetição no fim.
Considere também em como você modificaria a solução para escrever uma mensagem diferente da segunda leitura em diante.
Acumuladores
Variáveis em estruturas de repetição usadas para armazenar resultados parciais obtidos em iterações de interesse são chamadas de acumuladores. Acumuladores são comumente usados para contagens de elementos, somatórios, criação de cadeias de caracteres, vetores, estruturas de dados e de outros tipos compostos de dados.
Por exemplo, imagine uma situação na qual:
- Solicite-se cinco números para um usuário ou uma usuária final do programa;
- Deseje-se saber quantos desses números são negativos ou pares;
- Deva-se apresentar a quantidade de números identificados;
- Deva-se apresentar, também, a soma dos números negativos ou pares.
let contador_negativos = 0
let contador_pares = 0
let contador_negativos_ou_pares = 0
let soma_negativos_ou_pares = 0
for (let contador = 0; contador < 5; ++contador) {
let numero = parseInt(prompt("Digite um número: "))
let numero_negativo = (numero < 0)
// JavaScript retorna -0 para resto de divisão de número negativo.
// Para evitar o problema, pode-se obter o valor absoluto do número.
let numero_par = ((Math.abs(numero) % 2) == 0)
if (numero_negativo) {
++contador_negativos
}
if (numero_par) {
++contador_pares
}
if (numero_negativo || numero_par) {
++contador_negativos_ou_pares
soma_negativos_ou_pares += numero
}
}
console.log("Total de números negativos: ", contador_negativos)
console.log("Total de números pares: ", contador_pares)
console.log("Total de números negativos ou pares: ", contador_negativos_ou_pares)
console.log("Soma dos números negativos ou pares: ", soma_negativos_ou_pares)
contador_negativos = 0
contador_pares = 0
contador_negativos_ou_pares = 0
soma_negativos_ou_pares = 0
for contador in range(5):
numero = int(input("Digite um número: "))
numero_negativo = (numero < 0)
numero_par = ((numero % 2) == 0)
if (numero_negativo):
contador_negativos += 1
if (numero_par):
contador_pares += 1
if (numero_negativo or numero_par):
contador_negativos_ou_pares += 1
soma_negativos_ou_pares += numero
print("Total de números negativos: ", contador_negativos)
print("Total de números pares: ", contador_pares)
print("Total de números negativos ou pares: ", contador_negativos_ou_pares)
print("Soma dos números negativos ou pares: ", soma_negativos_ou_pares)
local contador_negativos = 0
local contador_pares = 0
local contador_negativos_ou_pares = 0
local soma_negativos_ou_pares = 0
for contador = 0, 4 do
print("Digite um número: ")
local numero = io.read("*number")
local numero_negativo = (numero < 0)
local numero_par = ((numero % 2) == 0)
if (numero_negativo) then
contador_negativos = contador_negativos + 1
end
if (numero_par) then
contador_pares = contador_pares + 1
end
if (numero_negativo or numero_par) then
contador_negativos_ou_pares = contador_negativos_ou_pares + 1
soma_negativos_ou_pares = soma_negativos_ou_pares + numero
end
end
print("Total de números negativos: ", contador_negativos)
print("Total de números pares: ", contador_pares)
print("Total de números negativos ou pares: ", contador_negativos_ou_pares)
print("Soma dos números negativos ou pares: ", soma_negativos_ou_pares)
extends Node
func _ready():
var contador_negativos = 0
var contador_pares = 0
var contador_negativos_ou_pares = 0
var soma_negativos_ou_pares = 0
for contador in range(5):
# Usando-se o índice como alternativa à falta de leitura via terminal.
var numero = contador
var numero_negativo = (numero < 0)
var numero_par = ((numero % 2) == 0)
if (numero_negativo):
contador_negativos += 1
if (numero_par):
contador_pares += 1
if (numero_negativo or numero_par):
contador_negativos_ou_pares += 1
soma_negativos_ou_pares += numero
print("Total de números negativos: ", contador_negativos)
print("Total de números pares: ", contador_pares)
print("Total de números negativos ou pares: ", contador_negativos_ou_pares)
print("Soma dos números negativos ou pares: ", soma_negativos_ou_pares)
Nas implementações, contador_negativos
, contador_pares
, contador_negativos_ou_pares
e soma_negativos_ou_pares
são acumuladores.
Existem outras formas de evitar contar duas vezes um número que seja negativo e par. Pense em algumas e modifique a solução. Além disso, como você simplificaria a solução caso se quisesse saber o total de números negativos e pares?
Interrompa (Break) e Continue
O comando interrompa
(break
) permite forçar o término de uma estrutura de repetição, ignorando-se (contornando-se) a condição originalmente definida.
Ele é usado, por exemplo, para terminar um laço definido como enquanto (Verdadeiro)
.
Embora eu recomende usá-lo com parcimônia (normalmente apenas caso não exista solução melhor), ele é uma ferramenta adicional para resolver problemas.
Também existe o comando continue
, que permite ignorar a iteração atual, avançando para a próxima repetição.
Deve-se atentar que Lua não define o comando.
let contador = 0
while (contador < 5) {
++contador
if (contador < 3) {
continue
}
console.log(contador, ". Olá, meu nome é Franco!")
}
contador = 0
while (contador < 5):
contador += 1
if (contador < 3):
continue
print(contador, ". Olá, meu nome é Franco!")
extends Node
func _ready():
var contador = 0
while (contador < 5):
contador += 1
if (contador < 3):
continue
print(contador, ". Olá, meu nome é Franco!")
Uma alternativa simples ao uso de continue
é usar uma estrutura condicional, como se
.
Para um código equivalente praticamente idêntico, pode-se usar um bloco vazio para a condição original.
Também seria possível invertê-la (contador >= 3
) para evitar o uso do bloco vazio.
let contador = 0
while (contador < 5) {
++contador
if (contador < 3) {
} else {
console.log(contador, ". Olá, meu nome é Franco!")
}
}
contador = 0
while (contador < 5) {
++contador
if (contador >= 3) {
console.log(contador, ". Olá, meu nome é Franco!")
}
}
contador = 0
while (contador < 5):
contador += 1
if (contador < 3):
pass
else:
print(contador, ". Olá, meu nome é Franco!")
contador = 0
while (contador < 5):
contador += 1
if (contador >= 3):
print(contador, ". Olá, meu nome é Franco!")
local contador = 0
while (contador < 5) do
contador = contador + 1
if (contador < 3) then
else
print(contador, ". Olá, meu nome é Franco!")
end
end
contador = 0
while (contador < 5) do
contador = contador + 1
if (contador >= 3) then
print(contador, ". Olá, meu nome é Franco!")
end
end
extends Node
func _ready():
var contador = 0
while (contador < 5):
contador += 1
if (contador < 3):
pass
else:
print(contador, ". Olá, meu nome é Franco!")
contador = 0
while (contador < 5):
contador += 1
if (contador >= 3):
print(contador, ". Olá, meu nome é Franco!")
Como Python e GDScript não permitem a definição de blocos vazios, usa-se pass
como um comando vazio.
Além disso, é importante notar que interrompa
e continue
não são, necessariamente, equivalentes.
Para constatar as diferenças, você pode executar um dos programas a seguir e comparar os resultados.
console.log("Continue")
let contador = 0
while (contador < 5) {
++contador
if (contador === 3) {
continue
}
console.log(contador, ". Olá, meu nome é Franco!")
}
console.log("Break")
contador = 0
while (contador < 5) {
++contador
if (contador === 3) {
break
}
console.log(contador, ". Olá, meu nome é Franco!")
}
print("Continue")
contador = 0
while (contador < 5):
contador += 1
if (contador == 3):
continue
print(contador, ". Olá, meu nome é Franco!")
print("Break")
contador = 0
while (contador < 5):
contador += 1
if (contador == 3):
break
print(contador, ". Olá, meu nome é Franco!")
extends Node
func _ready():
print("Continue")
var contador = 0
while (contador < 5):
contador += 1
if (contador == 3):
continue
print(contador, ". Olá, meu nome é Franco!")
print("Break")
contador = 0
while (contador < 5):
contador += 1
if (contador == 3):
break
print(contador, ". Olá, meu nome é Franco!")
O uso de continue
ignora apenas a repetição para o valor 3
, mas o uso de break
termina as repetições quando contador
tem valor 3
.
Em resumo, algumas linguagens definam continue
e interrompa
.
Entretanto, comumente há alternativas para evitar o uso dos comandos que tendem a resultar em código mais simples de ler.
Estruturas de Repetição Aninhadas
Assim como é possível aninhar estruturas de condição, também é possível aninhar estruturas de repetição. Um exemplo bastante simples estruturas aninhadas é uma tabela para tabuadas.
for (let multiplicador = 0; multiplicador < 11; ++multiplicador) {
console.log("Tabuada do ", multiplicador)
for (let multiplicando = 0; multiplicando < 11; ++multiplicando) {
console.log(multiplicador, " * ", multiplicando, " = ", multiplicador * multiplicando)
}
console.log("") // Adiciona uma linha vazia para separar resultados.
}
for multiplicador in range(11):
print("Tabuada do ", multiplicador)
for multiplicando in range(11):
print(multiplicador, " * ", multiplicando, " = ", multiplicador * multiplicando)
print("") # Adiciona uma linha vazia para separar resultados.
for multiplicador = 0, 10 do
print("Tabuada do ", multiplicador)
for multiplicando = 0, 10 do
print(multiplicador, " * ", multiplicando, " = ", multiplicador * multiplicando)
end
print("") -- Adiciona uma linha vazia para separar resultados.
end
extends Node
func _ready():
for multiplicador in range(11):
print("Tabuada do ", multiplicador)
for multiplicando in range(11):
print(multiplicador, " * ", multiplicando, " = ", multiplicador * multiplicando)
print("") # Adiciona uma linha vazia para separar resultados.
As implementações em Python e GDScript utilizam as convenções para omissão de valor inicial e incremento, sendo equivalente ao uso de range(0, 11, 1)
.
A implementação em Lua omite o incremento, sendo equivalente ao uso de for multiplicador = 1, 10, 1
.
Eu recomendaria fazer um teste de mesa para entender o funcionamento de estruturas de repetição aninhadas.
Deve-se notar que cada laço externo realiza a próxima iteração apenas ao final de todas as repetições do(s) laço(s) mais interno(s).
De fato, o programa realizou 121 multiplicações: 11 multiplicações para cada possível multiplicador
do laço mais externo.
Complexidade Computacional, Big Oh e Big Theta
O exemplo de tabuadas com estruturas de repetição aninhadas mostra que, com poucas linhas de código, é possível criar programas que realizem, potencialmente, centenas de operações. De fato, aumentando-se os limites finais, o programa poderia realizar milhares, milhões, bilhões de repetições. Cabe, pois, a pergunta: existem limites para quantidades de operações?
A complexidade computacional (ou complexidade algorítmica) estuda complexidade de algoritmos.
Para uma introdução simples e suficiente para responder a pergunta, porém intuitiva para mostrar como o uso de estruturas de repetição aninhadas multiplicam o número de instruções que serão realizadas em um programa, analise o programa abaixo.
Adicione ou remova níveis para verificar como o número de repetições aumentará ou diminuirá de um fator REPETICOES
.
O único requisito é que a linha incrementando o contador esteja no último nível da repetição.
const REPETICOES = 10
let total_repeticoes = 0
for (let nivel_1 = 0; nivel_1 < REPETICOES; ++nivel_1) {
for (let nivel_2 = 0; nivel_2 < REPETICOES; ++nivel_2) {
for (let nivel_3 = 0; nivel_3 < REPETICOES; ++nivel_3) {
for (let nivel_4 = 0; nivel_4 < REPETICOES; ++nivel_4) {
for (let nivel_5 = 0; nivel_5 < REPETICOES; ++nivel_5) {
++total_repeticoes
}
}
}
}
}
console.log(total_repeticoes)
from typing import Final
REPETICOES: Final = 10
total_repeticoes = 0
for nivel_1 in range(REPETICOES):
for nivel_2 in range(REPETICOES):
for nivel_3 in range(REPETICOES):
for nivel_4 in range(REPETICOES):
for nivel_5 in range(REPETICOES):
total_repeticoes += 1
print(total_repeticoes)
local REPETICOES <const> = 10
local total_repeticoes = 0
for nivel_1 = 1, REPETICOES do
for nivel_2 = 1, REPETICOES do
for nivel_3 = 1, REPETICOES do
for nivel_4 = 1, REPETICOES do
for nivel_5 = 1, REPETICOES do
total_repeticoes = total_repeticoes + 1
end
end
end
end
end
print(total_repeticoes)
extends Node
const REPETICOES = 10
func _ready():
var total_repeticoes = 0
for nivel_1 in range(REPETICOES):
for nivel_2 in range(REPETICOES):
for nivel_3 in range(REPETICOES):
for nivel_4 in range(REPETICOES):
for nivel_5 in range(REPETICOES):
total_repeticoes += 1
print(total_repeticoes)
Os exemplos anteriores tem complexidade dita polinomial (classe P).
Neste caso, é possível calcular o número de instruções realizadas elevando-se REPETICOES
pelo número de níveis:
- Para um nível, o programa realizaria
REPETICOES
incrementos (10, com o valor da constante). O programa teria complexidade linear, expresso em notação Big O (lê-se big oh) como ou em notação Big Theta como ; - Para dois níveis, o programa realizaria
REPETICOES
vezesREPETICOES
incrementos (100, com o valor da constante). O programa teria complexidade quadrática, expresso em notação como ou ; - Para três níveis, o programa realizaria
REPETICOES
vezesREPETICOES
vezesREPETICOES
incrementos (1000, com o valor da constante). O programa teria complexidade cúbica, expresso em notação como ou . A partir deste nível, costuma-se dizer complexidade polinomial; - Para quatro níveis, 10000 incrementos. O programa teria complexidade polinomial, expresso em notação como ou ;
- Para cinco níveis, o programa 100000 incrementos. O programa teria complexidade polinomial, expresso em notação como ou .
As notações Big Oh e Big Theta também são conhecidas como notações assimptóticas. O notação Big Theta exige mais rigor que a notação Big Oh, por fornecer um número mais próximo ao real. No caso de Big Oh, basta definir um limite superior para o qual o comportamento de uma função convirja, para se estimar a taxa de crescimento mediante o tamanho de entradas. Em outras palavras, Big Oh estima o pior caso possível para a execução do programa. Big Theta estima o caso médio, que normalmente (mas nem sempre) ocorrerá ao executar o programa. Existe também uma terceira notação chamada de Big Omega (), usada para estimar o melhor caso.
No exemplo, o número de instruções será sempre igual para um mesmo valor de REPETICOES
, então todas as notações terão o mesmo resultado.
Considerando-se REPETICOES
como o tamanho da entrada, pode-se estimar o crescimento exponencial do número de operações necessárias a cada novo nível de repetições considerado.
Caso REPETICOES
tivesse um valor como 1000 ao invés de 10, o número de operações necessárias cresceria muito mais rapidamente.
Nos 5 níveis, seriam realizadas repetições.
Ou seja, 1.000.000.000.000.000 (um quadrilhão) de repetições.
De fato, caso você altere o valor da constante no programa para 1000 ao invés de 10, o programa levará um tempo significativo para terminar. Por exemplo, pode-se assumir que cada repetição levasse 1 nanossegundo ( segundos). Multiplicando-se , ou seja, 1.000.000 segundos, que, dividido por 3600 (número de segundos em uma hora), resultaria em cerca de 277,78 horas.
Em outras palavras, computadores atuais possuem limites e problemas complexos requerendo o uso de muitas estruturas de repetição aninhadas podem tornar-se computacionalmente intratáveis rapidamente. Dependendo do tamanho ou da classe de complexidade, os melhores resultados possíveis com computadores atuais podem ser aproximações dos exatos.
Caso, por curiosidade, você queira estimar o tempo que seu computador leva para realizar uma repetição no programa definido, pode-se medir o tempo necessário usando-se um contador de desempenho (performance counter) ou um temporizador (timer). Para um resultado mais próximo do real, o ideal é repetir o trecho desejado várias vezes para evitar interferências.
Por exemplo:
- Em JavaScript, pode-se usar
performance.now()
(documentação). O tempo é medido em milissegundos ( segundos); - Em Python, pode-se usar
time.perf_counter()
(documentação).time.perf_counter()
mede o tempo em segundos usando um temporizador de alta precisão. - Em Lua, não há um temporizador de alta precisão disponível por padrão.
Uma alternativa é obter o tempo e subtraí-lo para aproximar o resultado.
Para isso, pode-se usar
os.time()
para obter o horário da máquina (documentação) eos.difftime()
para calcular a diferença de tempo em segundos (documentação). Infelizmente, a precisão máxima é segundos, então o código precisará ser repetido para que a execução leve alguns segundos. - Em GDScript, pode-se usar
OS.get_ticks_usec()
para tempos em microssegundos ( segundos; documentação) ouOS.get_ticks_msec()
para tempos em milissegundos (documentação).
Apenas por curiosidade, uma implementação em C++ também é apresentada (embora ela não será explicada).
const REPETICOES = 10
let total_repeticoes = 0
inicio = performance.now()
for (let nivel_1 = 0; nivel_1 < REPETICOES; ++nivel_1) {
for (let nivel_2 = 0; nivel_2 < REPETICOES; ++nivel_2) {
for (let nivel_3 = 0; nivel_3 < REPETICOES; ++nivel_3) {
for (let nivel_4 = 0; nivel_4 < REPETICOES; ++nivel_4) {
for (let nivel_5 = 0; nivel_5 < REPETICOES; ++nivel_5) {
++total_repeticoes
}
}
}
}
}
fim = performance.now()
console.log("Total de repetições: ", total_repeticoes)
tempo_uma_repeticao_segundos = (fim - inicio) / total_repeticoes * 1000.0
console.log("Tempo por repetição:", tempo_uma_repeticao_segundos, " s (", tempo_uma_repeticao_segundos * 1.0e9 ," ns).")
import time
from typing import Final
REPETICOES: Final = 10
total_repeticoes = 0
inicio = time.perf_counter()
for nivel_1 in range(REPETICOES):
for nivel_2 in range(REPETICOES):
for nivel_3 in range(REPETICOES):
for nivel_4 in range(REPETICOES):
for nivel_5 in range(REPETICOES):
total_repeticoes += 1
fim = time.perf_counter()
print("Total de repetições: ", total_repeticoes)
tempo_uma_repeticao_segundos = (fim - inicio) / total_repeticoes
print("Tempo por repetição:", tempo_uma_repeticao_segundos, " s (", tempo_uma_repeticao_segundos * 1.0e9 ," ns).")
local REPETICOES <const> = 10
local total_repeticoes = 0
local inicio = os.time()
-- Repetições adicionais para que a execução leve alguns segundos.
for i = 1, 1000 do for nivel_1 = 1, REPETICOES do
for nivel_2 = 1, REPETICOES do
for nivel_3 = 1, REPETICOES do
for nivel_4 = 1, REPETICOES do
for nivel_5 = 1, REPETICOES do
total_repeticoes = total_repeticoes + 1
end
end
end
end
end
end
local fim = os.time()
print(total_repeticoes)
local tempo_uma_repeticao_segundos = os.difftime(fim, inicio) / total_repeticoes
print("Tempo por repetição:", tempo_uma_repeticao_segundos, " s (", tempo_uma_repeticao_segundos * 1.0e9 ," ns).")
extends Node
const REPETICOES = 10
func _ready():
var total_repeticoes = 0
var inicio = OS.get_ticks_usec()
for nivel_1 in range(REPETICOES):
for nivel_2 in range(REPETICOES):
for nivel_3 in range(REPETICOES):
for nivel_4 in range(REPETICOES):
for nivel_5 in range(REPETICOES):
total_repeticoes += 1
var fim = OS.get_ticks_usec()
print("Total de repetições: ", total_repeticoes)
var tempo_uma_repeticao_segundos = (fim - inicio) / (1.0e6 * total_repeticoes)
print("Tempo por repetição:", tempo_uma_repeticao_segundos, " s (", tempo_uma_repeticao_segundos * 1.0e9 ," ns).")
# Formatação personalizada para exibir valores decimais com mais casas (15).
print("Tempo por repetição: %0.15f s." % tempo_uma_repeticao_segundos)
// g++ main.c && ./a.out
#include <chrono>
#include <iomanip>
#include <iostream>
int main()
{
const int REPETICOES = 10;
// Inteiro normalmente definido com 64 ou 128 bits.
long long total_repeticoes = 0;
auto inicio = std::chrono::high_resolution_clock::now();
for (int nivel_1 = 0; nivel_1 < REPETICOES; ++nivel_1)
{
for (int nivel_2 = 0; nivel_2 < REPETICOES; ++nivel_2)
{
for (int nivel_3 = 0; nivel_3 < REPETICOES; ++nivel_3)
{
for (int nivel_4 = 0; nivel_4 < REPETICOES; ++nivel_4)
{
for (int nivel_5 = 0; nivel_5 < REPETICOES; ++nivel_5)
{
++total_repeticoes;
}
}
}
}
}
auto fim = std::chrono::high_resolution_clock::now();
double tempo_uma_repeticao_segundos =
std::chrono::duration<double>(fim - inicio).count() / total_repeticoes;
std::cout << std::setprecision(15)
<< "Tempo por repetição: " << tempo_uma_repeticao_segundos << " s ("
<< tempo_uma_repeticao_segundos * 1.0e9 << " ns)." << std::endl;
return 0;
}
Algo a atentar é que, dependendo do valor escolhido para REPETICOES
, pode ocorrer overflow em algumas linguagens de programação (caso total_repeticoes
exceda o maior valor inteiro que possa ser armazenado).
Na implementação em Lua, é importante notar que não se modificou o valor de REPETICOES
, mas se executou o código 1000.
Assim, o código foi repetido vezes, ou seja, 100.000.000 (cem milhões; um número, portanto, 10 milhões de vezes menor que ).
Por curiosidade, os resultados em meu computador foram:
- JavaScript em Firefox: 259.99999999999994 nanossegundos (ns) por repetição;
- Python: 363.8855700046406 ns por repetição;
- Lua: 9.99000999001 ns por repetição;
- GDScript: 81.34 ns por repetição.
A implementação em GDScript emprega uma chamada adicional para escrita dado que o primeiro resultado (0) é um erro de aproximação de
print()
. Para contorná-lo, pode-se especificar o número de casas decimais desejadas para a escrita, feita na linha seguinte de código.
É interessante notar, assim, que, no caso deste exemplo, Lua é significativamente mais rápida que todas as outras linguagens de programação consideradas.
Como Lua e as outras linguagens são interpretadas (e não se usou LuaJIT), o código em uma linguagem compilada como C++ normalmente seria ainda mais rápido. Por curiosidade, a execução do programa anterior em C++ levou 1.33741e-09 s (1.33741 ns) por repetição na mesma máquina, em uma compilação padrão sem otimizações.
Para resultados mais precisos, pode-se adotar a mesma estratégia usada em Lua para aumentar o número de repetições usando um laço externo.
O programa criado é bastante simples, não sendo representativo de cenários de uso reais. Contudo, embora a diferença tenda a variar para programas mais complexos, a comparação ilustra como linguagens compiladas costumam ser mais rápidas que interpretadas. Além disso, o exemplo também mostrar porque Lua é comumente usada como linguagem de scripting em jogos: mesmo o interpretador padrão é bastante rápido para padrões de linguagens interpretadas.
Exemplos
Estruturas de repetição podem ser difíceis de entender e usar por pessoas iniciantes em programação. Algo que pode ajudar é pensar em resolver um problema de forma genérica para um único elemento, como se estivesse criando uma subrotina. Em seguida, basta adicionar uma estrutura de repetição para aplicar a solução para diversos valores. Além de benéfico, pensar e programar dessa forma gera código modular.
Fatorial
A implementação recursiva para cálculo do fatorial foi apresentada como uma subrotina. Com estruturas de repetição, é possível implementar uma solução iterativa para o problema.
Como mencionado na página citada, da definição:
"Um número fatorial é um número gerado pela expressão , sendo (lê-se zero fatorial) definido como e um número natural. Por exemplo, ."
Uma observação atenta revela que a definição e o exemplo constituem uma seqüência de números que pode ser gerada por uma estrutura de repetição. Ela é, simplesmente, o produtório de uma contagem regressiva do número até , que corresponde a .
let numero = 5
let fatorial = 1
for (let multiplicador = numero; multiplicador > 0; --multiplicador) {
fatorial = fatorial * multiplicador
}
console.log(fatorial)
numero = 5
fatorial = 1
for multiplicador in range(numero, 0, -1):
fatorial = fatorial * multiplicador
print(fatorial)
local numero = 5
local fatorial = 1
for multiplicador = numero, 1, -1 do
fatorial = fatorial * multiplicador
end
print(fatorial)
extends Node
func _ready():
var numero = 5
var fatorial = 1
for multiplicador in range(numero, 0, -1):
fatorial = fatorial * multiplicador
print(fatorial)
Caso se quisesse, seria possível terminar a repetição com a última multiplicação por 2 ao invés de 1, dado que a multiplicação por 1 não altera o resultado. Além disso, como a multiplicação é associativa e comutativa, poder-se-ia alterar a implementação para incrementar os números em ordem crescente. Os próximos exemplos fazem ambas as alterações.
let numero = 5
let fatorial = 1
for (let multiplicador = 2; multiplicador <= numero; ++multiplicador) {
fatorial = fatorial * multiplicador
}
console.log(fatorial)
numero = 5
fatorial = 1
for multiplicador in range(2, numero + 1):
fatorial = fatorial * multiplicador
print(fatorial)
local numero = 5
local fatorial = 1
for multiplicador = 2, numero do
fatorial = fatorial * multiplicador
end
print(fatorial)
extends Node
func _ready():
var numero = 5
var fatorial = 1
for multiplicador in range(2, numero + 1):
fatorial = fatorial * multiplicador
print(fatorial)
Assim, quando se trabalhar com operações que sejam comutativas e associativas, pode ser conveniente alterar a ordem de fatores para facilitar a implementação de uma solução. Entretanto, existem dados e operações que não são comutativos, como multiplicação de matrizes. Logo, convém tomar os devidos cuidados para cada problema.
Estruturas de Repetição com Subrotinas
Para melhorar a solução criada para o cálculo fatorial, pode-se adicionar estruturas condicionais para tratamento de erros e também refatorar a solução em uma função.
Para a função, numero
pode tornar-se o parâmetro de entrada, enquanto fatorial
será o resultado.
function fatorial(numero) {
if (numero < 0) {
return -1
}
let fatorial = 1
for (let multiplicador = 2; multiplicador <= numero; ++multiplicador) {
fatorial *= multiplicador
}
return fatorial
}
console.log(fatorial(5))
def fatorial(numero):
if (numero < 0):
return -1
fatorial = 1
for multiplicador in range(2, numero + 1):
fatorial *= multiplicador
return fatorial
print(fatorial(5))
function fatorial(numero)
if (numero < 0) then
return -1
end
local fatorial = 1
for multiplicador = 2, numero do
fatorial = fatorial * multiplicador
end
return fatorial
end
print(fatorial(5))
extends Node
func fatorial(numero):
if (numero < 0):
return -1
var fatorial = 1
for multiplicador in range(2, numero + 1):
fatorial *= multiplicador
return fatorial
func _ready():
print(fatorial(5))
É interessante notar que a função definida utiliza muitos dos conceitos estudados até este momento.
Contudo, embora JavaScript, Python, Lua e GDScript permitam definir um mesmo nome para uma função e uma variável, isso pode ser confuso.
Assim, caso se queira, pode-se renomear a variável fatorial
para algo como resultado
.
Além da renomeação, uma oportunidade para ilustrar outro uso de estrutura de repetição seria imprimir uma lista de números fatoriais usando um laço que chamasse a função fatorial()
para diversos valores.
function fatorial(numero) {
if (numero < 0) {
return -1
}
let resultado = 1
for (let multiplicador = 2; multiplicador <= numero; ++multiplicador) {
resultado *= multiplicador
}
return resultado
}
for (let i = 0; i < 26; ++i) {
console.log(i, "! = ", fatorial(i))
}
def fatorial(numero):
if (numero < 0):
return -1
resultado = 1
for multiplicador in range(2, numero + 1):
resultado *= multiplicador
return resultado
for i in range(26):
print(i, "! = ", fatorial(i))
function fatorial(numero)
if (numero < 0) then
return -1
end
local resultado = 1
for multiplicador = 2, numero do
resultado = resultado * multiplicador
end
return resultado
end
for i = 0, 25 do
print(i, "! = ", fatorial(i))
end
extends Node
func fatorial(numero):
if (numero < 0):
return -1
var resultado = 1
for multiplicador in range(2, numero + 1):
resultado *= multiplicador
return resultado
func _ready():
for i in range(26):
print(i, "! = ", fatorial(i))
A tabela criada lista números fatoriais de até . Ela é uma boa forma de identificar a ocorrência de overflows em números inteiros. Dependendo da linguagem de programação (isto é, se a linguagem não fornece números inteiros de precisão arbitrária), alguns resultados podem ser negativos ou menores que os anteriores, indicando a ocorrência de overflow.
Somatório e Séries Matemáticas
Uma fórmula de calcular a constante matemática (pi, aproximadamente 3,141592) é multiplicar por 4 a seguinte série matemática:
A série é infinita; o resultado calculado aproxima o valor de . Quanto maior o número de termos adotado, mais próximo será o resultado do valor de .
Com os conhecimentos de programação adquiridos até este momento, já é possível definir um algoritmo para implementá-la. Para isso, o primeiro passo é tentar identificar padrões para cada termo.
Um ajuste para o primeiro termo pode ajudar a identificar um dos padrões.
Observando-se a fórmula com atenção, pode-se observar que:
- O numerador de cada termo sempre é 1;
- O denominador de cada termo é incrementado de 2 em 2;
- A operação alterna-se entre uma subtração e uma soma a cada novo termo.
O segundo item pode ser implementado usando uma estrutura de repetição que incremente o contador de 2 a cada passo. O resultado da expressão pode ser armazenado em um acumulador. O número de termos é de sua escolha. Por exemplo, você pode escolher usar dez, mil ou um milhão de termos. Você também pode definir uma constante. Quanto mais termos, mais próximo será o resultado; contudo, maior será o tempo necessário para calcular o resultado.
const NUMERO_TERMOS = 1000
let pi_4 = 0.0
let numerador = 1.0
let denominador = 1.0
for (let termo = 0; termo < NUMERO_TERMOS; ++termo) {
if (termo % 2 === 0) {
pi_4 += numerador / denominador
} else {
pi_4 -= numerador / denominador
}
// console.log("PI/4 para ", termo, " termo(s): ", 4.0 * pi_4)
denominador += 2.0
}
let pi = 4.0 * pi_4
console.log("PI/4 = ", pi_4)
console.log("PI = ", pi)
from typing import Final
NUMERO_TERMOS: Final = 1000
pi_4 = 0.0
numerador = 1.0
denominador = 1.0
for termo in range(NUMERO_TERMOS):
if (termo % 2 == 0):
pi_4 += numerador / denominador
else:
pi_4 -= numerador / denominador
# print("PI/4 para ", termo, " termo(s): ", 4.0 * pi_4)
denominador += 2.0
pi = 4.0 * pi_4
print("PI/4 = ", pi_4)
print("PI = ", pi)
local NUMERO_TERMOS <const> = 1000
local pi_4 = 0.0
local numerador = 1.0
local denominador = 1.0
for termo = 0, (NUMERO_TERMOS - 1) do
if (termo % 2 == 0) then
pi_4 = pi_4 + (numerador / denominador)
else
pi_4 = pi_4 - (numerador / denominador)
end
-- print("PI/4 para ", termo, " termo(s): ", 4.0 * pi_4)
denominador = denominador + 2.0
end
local pi = 4.0 * pi_4
print("PI/4 = ", pi_4)
print("PI = ", pi)
extends Node
const NUMERO_TERMOS = 1000
func _ready():
var pi_4 = 0.0
var numerador = 1.0
var denominador = 1.0
for termo in range(NUMERO_TERMOS):
if (termo % 2 == 0):
pi_4 += numerador / denominador
else:
pi_4 -= numerador / denominador
# print("PI/4 para ", termo, " termo(s): ", 4.0 * pi_4)
denominador += 2.0
var pi = 4.0 * pi_4
print("PI/4 = ", pi_4)
print("PI = ", pi)
Caso se queira observar o resultado parcial a cada passo do laço, pode-se remover o comentário dentro da estrutura de repetição. É interessante notar que o tempo de execução do programa será maior caso se escreva cada resultado intermediário. Isso ocorre porque operações de entrada e saída de dados normalmente demandam maior tempo por instrução que operadores lógicas, relacionais ou aritméticas.
Adivinhar Palavras
A maioria dos exemplos até este momento utilizam números e Matemática, por serem mais simples de operar.
Contudo, estruturas de repetição podem ser usadas para manipular qualquer tipo de dados.
Um exemplo foi o laço que terminava quando o programa recebia o valor fim
.
Um segundo exemplo simples é um algoritmo que solicita que a pessoa adivinhe uma palavra. A implementação pode fornecer algumas dicas após certos números de palpites, além de impor um limite máximo para tentativas. As dicas são convenientes porque, neste momento, ainda não foi apresentado como verificar caracteres individuais de uma cadeia de caracteres. Assim, o palpite poderia ser qualquer cadeia de caracteres válida, o que dificultaria bastante o jogo.
Como este será um dos primeiros programas que não envolvem a tradução direta de uma fórmula ou expressão, convém praticar o uso de pensamento computacional para criar a solução. O objetivo da implementação é transformar o problema em algo que o computador possa seguir para atingir a solução.
O que é necessário para escrever um algoritmo para um jogo de adivinhar palavras?
Na implementação mais simples:
- Armazenar a palavra secreta;
- Solicitar a entrada de um palpite;
- Comparar o palpite com a palavra secreta:
- Se o palpite estiver correto, o jogo termina com vitória.
- Se o palpite estiver incorreto, o jogo termina com derrota.
Para a primeira versão, basta uma comparação e/ou estrutura condicional para se determinar o resultado. Tente implementar a solução antes de consultar o exemplo abaixo. Para mostrar a solução, clique em "Mostrar/Ocultar Solução Parcial".
Mostrar/Ocultar Solução Parcial
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = PALAVRA_SECRETA.toLowerCase()
let palpite = ""
palpite = prompt("Escolha uma palavra.")
if (palpite.toLowerCase() === PALAVRA_SECRETA_MINUSCULAS) {
console.log("Parabéns!")
console.log("Você acertou na primeira tentativa!")
console.log("Você tem super poderes, muita sorte ou leu o código-fonte.")
} else {
console.log("Uma pena!")
console.log("A palavra era ", PALAVRA_SECRETA)
}
from typing import Final
PALAVRA_SECRETA: Final = "Franco"
PALAVRA_SECRETA_MINUSCULAS: Final = PALAVRA_SECRETA.lower()
palpite = input("Escolha uma palavra. ")
if (palpite.lower() == PALAVRA_SECRETA_MINUSCULAS):
print("Parabéns!")
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else:
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
local PALAVRA_SECRETA <const> = "Franco"
local PALAVRA_SECRETA_MINUSCULAS <const> = PALAVRA_SECRETA:lower()
print("Escolha uma palavra.")
local palpite = io.read("*line")
if (palpite:lower() == PALAVRA_SECRETA_MINUSCULAS) then
print("Parabéns!")
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
end
extends Node
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = "franco" # PALAVRA_SECRETA.to_lower()
func _ready():
var palpite = ""
if (palpite.to_lower() == PALAVRA_SECRETA_MINUSCULAS):
print("Parabéns!")
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else:
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
A implementação mais simples serve como núcleo (core) da solução, protótipo e prova de conceito sobre a viabilidade da solução. Daqui em diante, você pode melhorá-la com mais funcionalidades.
Para uma primeira melhoria, seria possível solicitar novos palpites até um acerto.
- Armazenar a palavra secreta;
- Repetir:
- Solicitar a entrada de um palpite;
- Comparar o palpite com a palavra secreta:
- Se o palpite estiver correto, o jogo termina com vitória.
- Se o palpite estiver incorreto, o programa retorna para o Passo 2.1.
Tente implementar a solução antes de consultar o exemplo abaixo.
Mostrar/Ocultar Solução Parcial
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = PALAVRA_SECRETA.toLowerCase()
let palpite = ""
while (palpite.toLowerCase() !== PALAVRA_SECRETA_MINUSCULAS) {
palpite = prompt("Escolha uma palavra.")
}
console.log("Parabéns!")
from typing import Final
PALAVRA_SECRETA: Final = "Franco"
PALAVRA_SECRETA_MINUSCULAS: Final = PALAVRA_SECRETA.lower()
palpite = ""
while (palpite.lower() != PALAVRA_SECRETA_MINUSCULAS):
palpite = input("Escolha uma palavra. ")
print("Parabéns!")
local PALAVRA_SECRETA <const> = "Franco"
local PALAVRA_SECRETA_MINUSCULAS <const> = PALAVRA_SECRETA:lower()
local palpite = ""
while (palpite:lower() ~= PALAVRA_SECRETA_MINUSCULAS) do
print("Escolha uma palavra.")
palpite = io.read("*line")
end
print("Parabéns!")
extends Node
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = "franco" # PALAVRA_SECRETA.to_lower()
func _ready():
var palpite = ""
while (palpite.to_lower() != PALAVRA_SECRETA_MINUSCULAS):
palpite = "FRANCO"
print("Parabéns!")
Uma segunda melhoria poderia incluir a definição de um número máximo de palpites.
- Armazenar a palavra secreta;
- Armazenar um número máximo de palpites;
- Iniciar um contador de palpites.
- Repetir:
- Incrementar contador de palpites;
- Comparar o contador de palpites com o número máximo permitido:
- Se o número for maior, o programa termina com derrota.
- Se o número for menor ou igual:
- Solicitar a entrada de um palpite;
- Comparar o palpite com a palavra secreta:
- Se o palpite estiver correto, o jogo termina com vitória.
- Se o palpite estiver incorreto, o programa retorna para o Passo 4.1.
Tente implementar a solução antes de consultar o exemplo abaixo.
Na resposta fornecida, algumas implementações usam repita
ao invés de enquanto
para ilustrar o uso do comando de repetição com verificação no fim.
Caso sua resposta baseie-se nos exemplos anteriores com enquanto
, você pode continuar a usar a estrutura ao invés de mudá-la.
Mostrar/Ocultar Solução Parcial
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = PALAVRA_SECRETA.toLowerCase()
const NUMERO_MAXIMO_TENTATIVAS = 150
let palpite = ""
let numero_tentativas = 0
let palpite_correto = false
do {
++numero_tentativas
palpite = prompt("Escolha uma palavra.")
palpite_correto = (palpite.toLowerCase() === PALAVRA_SECRETA_MINUSCULAS)
} while ((!palpite_correto) && (numero_tentativas < NUMERO_MAXIMO_TENTATIVAS))
if (palpite_correto) {
console.log("Parabéns!")
if (numero_tentativas === 1) {
console.log("Você acertou na primeira tentativa!")
console.log("Você tem super poderes, muita sorte ou leu o código-fonte.")
} else {
console.log("Você acertou a palavra após ", numero_tentativas, " tentativas.")
}
} else {
console.log("Uma pena!")
console.log("A palavra era ", PALAVRA_SECRETA)
}
from typing import Final
PALAVRA_SECRETA: Final = "Franco"
PALAVRA_SECRETA_MINUSCULAS: Final = PALAVRA_SECRETA.lower()
NUMERO_MAXIMO_TENTATIVAS: Final = 150
palpite = ""
numero_tentativas = 0
palpite_correto = False
while ((not palpite_correto) and (numero_tentativas < NUMERO_MAXIMO_TENTATIVAS)):
numero_tentativas += 1
palpite = input("Escolha uma palavra. ")
palpite_correto = (palpite.lower() == PALAVRA_SECRETA_MINUSCULAS)
if (palpite_correto):
print("Parabéns!")
if (numero_tentativas == 1):
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else:
print("Você acertou a palavra após ", numero_tentativas, " tentativas.")
else:
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
local PALAVRA_SECRETA <const> = "Franco"
local PALAVRA_SECRETA_MINUSCULAS <const> = PALAVRA_SECRETA:lower()
local NUMERO_MAXIMO_TENTATIVAS <const> = 150
local palpite = ""
local numero_tentativas = 0
local palpite_correto = False
repeat
numero_tentativas = numero_tentativas + 1
print("Escolha uma palavra.")
palpite = io.read("*line")
palpite_correto = (palpite:lower() == PALAVRA_SECRETA_MINUSCULAS)
until ((palpite_correto) or (numero_tentativas >= NUMERO_MAXIMO_TENTATIVAS))
if (palpite_correto) then
print("Parabéns!")
if (numero_tentativas == 1) then
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else
print("Você acertou a palavra após ", numero_tentativas, " tentativas.")
end
else
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
end
extends Node
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = "franco" # PALAVRA_SECRETA.to_lower()
const NUMERO_MAXIMO_TENTATIVAS = 150
func _ready():
var palpite = ""
var numero_tentativas = 0
var palpite_correto = false
while ((not palpite_correto) and (numero_tentativas < NUMERO_MAXIMO_TENTATIVAS)):
numero_tentativas += 1
# palpite = input("Escolha uma palavra. ")
palpite_correto = (palpite.to_lower() == PALAVRA_SECRETA_MINUSCULAS)
if (palpite_correto):
print("Parabéns!")
if (numero_tentativas == 1):
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else:
print("Você acertou a palavra após ", numero_tentativas, " tentativas.")
else:
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
Uma terceira melhoria poderia incluir dicas em determinados números de palpites.
- Armazenar a palavra secreta;
- Armazenar um número máximo de palpites;
- Iniciar um contador de palpites.
- Repetir:
- Incrementar contador de palpites;
- Comparar o contador de palpites com o número máximo permitido:
- Se o número for maior, o programa termina com derrota.
- Se o número for menor ou igual:
- Caso o número da tentativa seja um dos pré-definidos, apresentar uma dica;
- Solicitar a entrada de um palpite;
- Comparar o palpite com a palavra secreta:
- Se o palpite estiver correto, o jogo termina com vitória.
- Se o palpite estiver incorreto, o programa retorna para o Passo 4.1.
Com a mudança, o código resultante poderia ficar como a seguir.
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = PALAVRA_SECRETA.toLowerCase()
const NUMERO_MAXIMO_TENTATIVAS = 150
let palpite = ""
let numero_tentativas = 0
let palpite_correto = false
do {
switch (numero_tentativas) {
case 3:
console.log("A palavra começa com 'f'.")
break
case 5:
console.log("A palavra termina com 'o'.")
break
case 8:
console.log("A palavra possui seis letras.")
break
case 10:
console.log("A palavra está no endereço desta página.")
break
case 30:
console.log("F _ _ n _ o")
break
case 60:
console.log("F _ a n _ o")
break
case 100:
console.log("F r a n _ o")
break
default:
break
}
++numero_tentativas
palpite = prompt("Escolha uma palavra.")
palpite_correto = (palpite.toLowerCase() === PALAVRA_SECRETA_MINUSCULAS)
} while ((!palpite_correto) && (numero_tentativas < NUMERO_MAXIMO_TENTATIVAS))
if (palpite_correto) {
console.log("Parabéns!")
if (numero_tentativas === 1) {
console.log("Você acertou na primeira tentativa!")
console.log("Você tem super poderes, muita sorte ou leu o código-fonte.")
} else {
console.log("Você acertou a palavra após ", numero_tentativas, " tentativas.")
}
} else {
console.log("Uma pena!")
console.log("A palavra era ", PALAVRA_SECRETA)
}
from typing import Final
PALAVRA_SECRETA: Final = "Franco"
PALAVRA_SECRETA_MINUSCULAS: Final = PALAVRA_SECRETA.lower()
NUMERO_MAXIMO_TENTATIVAS: Final = 150
palpite = ""
numero_tentativas = 0
palpite_correto = False
while ((not palpite_correto) and (numero_tentativas < NUMERO_MAXIMO_TENTATIVAS)):
if (numero_tentativas == 3):
print("A palavra começa com 'f'.")
elif (numero_tentativas == 5):
print("A palavra termina com 'o'.")
elif (numero_tentativas == 8):
print("A palavra possui seis letras.")
elif (numero_tentativas == 10):
print("A palavra está no endereço desta página.")
elif (numero_tentativas == 30):
print("F _ _ n _ o")
elif (numero_tentativas == 60):
print("F _ a n _ o")
elif (numero_tentativas == 100):
print("F r a n _ o")
numero_tentativas += 1
palpite = input("Escolha uma palavra. ")
palpite_correto = (palpite.lower() == PALAVRA_SECRETA_MINUSCULAS)
if (palpite_correto):
print("Parabéns!")
if (numero_tentativas == 1):
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else:
print("Você acertou a palavra após ", numero_tentativas, " tentativas.")
else:
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
local PALAVRA_SECRETA <const> = "Franco"
local PALAVRA_SECRETA_MINUSCULAS <const> = PALAVRA_SECRETA:lower()
local NUMERO_MAXIMO_TENTATIVAS <const> = 150
local palpite = ""
local numero_tentativas = 0
local palpite_correto = False
repeat
if (numero_tentativas == 3) then
print("A palavra começa com 'f'.")
elseif (numero_tentativas == 5) then
print("A palavra termina com 'o'.")
elseif (numero_tentativas == 8) then
print("A palavra possui seis letras.")
elseif (numero_tentativas == 10) then
print("A palavra está no endereço desta página.")
elseif (numero_tentativas == 30) then
print("F _ _ n _ o")
elseif (numero_tentativas == 60) then
print("F _ a n _ o")
elseif (numero_tentativas == 100) then
print("F r a n _ o")
end
numero_tentativas = numero_tentativas + 1
print("Escolha uma palavra.")
palpite = io.read("*line")
palpite_correto = (palpite:lower() == PALAVRA_SECRETA_MINUSCULAS)
until ((palpite_correto) or (numero_tentativas >= NUMERO_MAXIMO_TENTATIVAS))
if (palpite_correto) then
print("Parabéns!")
if (numero_tentativas == 1) then
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else
print("Você acertou a palavra após ", numero_tentativas, " tentativas.")
end
else
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
end
extends Node
const PALAVRA_SECRETA = "Franco"
const PALAVRA_SECRETA_MINUSCULAS = "franco" # PALAVRA_SECRETA.to_lower()
const NUMERO_MAXIMO_TENTATIVAS = 150
func _ready():
var palpite = ""
var numero_tentativas = 0
var palpite_correto = false
while ((not palpite_correto) and (numero_tentativas < NUMERO_MAXIMO_TENTATIVAS)):
match (numero_tentativas):
3:
print("A palavra começa com 'f'.")
5:
print("A palavra termina com 'o'.")
8:
print("A palavra possui seis letras.")
10:
print("A palavra está no endereço desta página.")
30:
print("F _ _ n _ o")
60:
print("F _ a n _ o")
100:
print("F r a n _ o")
127:
palpite = "FRANCO"
numero_tentativas += 1
# palpite = input("Escolha uma palavra. ")
palpite_correto = (palpite.to_lower() == PALAVRA_SECRETA_MINUSCULAS)
if (palpite_correto):
print("Parabéns!")
if (numero_tentativas == 1):
print("Você acertou na primeira tentativa!")
print("Você tem super poderes, muita sorte ou leu o código-fonte.")
else:
print("Você acertou a palavra após ", numero_tentativas, " tentativas.")
else:
print("Uma pena!")
print("A palavra era ", PALAVRA_SECRETA)
Uma versão com mais recursos poderia implementar um jogo de forca, solicitando letras individuais e apresentado os palpites corretos até o momento. Contudo, deve-se saber mais sobre vetores (arrays) e cadeias de caracteres antes de tentar implementá-la. Eles serão discutidos em próximos tópicos.
De qualquer forma, o mais importante é perceber como soluções computacionais podem ser construída iterativamente, com protótipos cada vez mais complexos até chegar-se à solução desejada (ou uma satisfatória).
Números Aleatórios (Pseudoaleatórios) e Geradores de Números Aleatórios (RNGs / PRNGs)
Embora ainda não seja possível adicionar funcionalidades mais avançadas no programa para adivinhar uma palavra, é possível melhorar mais a solução caso ela utilize números. Assim, pode-se modificar a solução anterior para se adivinhar um número.
Ao invés de definir um número fixo como segredo no programa, esta seção introduz um recurso útil comumente disponível em subrotinas de linguagens de programação: a geração de números aleatórios. Mais tecnicamente, números aleatórios em linguagens de programação costumam ser definidos como números pseudoaleatórios, construídos por meio de modelos matemáticos ou estatísticos.
Um gerador de números aleatórios é popularmente conhecido pela sigla RNG, do termo em inglês Random Number Generator. Para manter o termo técnico, pode-se usar PRNG para Pseudorandom Number Generator, ou seja, um gerador de números pseudoaleatórios.
PRNGs são bastante usados em jogos digitais, simulações, geração procedural de conteúdo, ou mesmo para adicionar comportamentos aleatórios (estocásticos) em programas simples.
Linguagens de programação comumente fornecem três subrotinas para uso básico de números aleatórios com distribuição uniforme (potencialmente enviesada):
- Uma função para a geração de números pseudoaleatórios do tipo real, normalmente com valores entre 0.0 e 1.0 (normalmente como );
- Uma função para a geração de números pseudoaleatórios do tipo inteiro, normalmente com valores entre 0 e um valor máximo dependente de implementação;
- Um procedimento para inicialização da semente (seed) usada para determinar a seqüência de números pseudoaleatórios utilizada. Uma mesma raiz sempre gera a mesma seqüência de números, algo que pode ser útil para testar programar.
Nem toda linguagem de programação fornece as três subrotinas em bibliotecas padrão, mas é bastante comum existir, ao menos, o procedimento para inicialização de semente e uma das funções para geração de um número.
- JavaScript:
Math.random()
(documentação) gera números entre . A linguagem não fornece um procedimento para escolha de semente, nem uma função para geração de números inteiros. A escolha da semente é feita pela implementação. - Python: o módulo
random
provê várias subrotinas para uso de números pseudoaleatórios, incluindo:random.seed()
(documentação) para definição da semente;random.random()
(documentação) para sorteio de valor decimal entre ;random.randint()
(documentação) para sorteio de número inteiro entre ;
- Lua: (documentação);
math.randomseed()
(documentação) para definição da semente;math.random()
(documentação) para sorteio de valor decimal entre ;math.random(max)
(documentação) para sorteio de número inteiro entre ;math.random(min, max)
(documentação) para sorteio de número inteiro entre ;
- GDScript: (documentação).
rand_seed()
(documentação) para definição da semente;randomize()
(documentação) para definição de semente pelo motor;randf()
(documentação) para sorteio de valor decimal entre ;randi()
(documentação) para sorteio de número inteiro. Para se definir o intervalo, pode-se usar o resto de divisão. Por exemplo,randi() % 10 + 1
gera números entre ;rand_range(min, max)
(documentação) para sorteio de valor real entre .
Em linguagens que forneçam apenas funções para obtenção de números decimais entre , é possível obter um número inteiro em um intervalo arbitrário fazendo floor(min + random() * (max + 1 - min))
.
Matematicamente: .
Para números no intervalo , basta omitir a adição de 1.
Para linguagens que não definam intervalos para números inteiros, pode-se usar o resto de divisão para a definição de intervalos arbitrários, como comentado em GDScript.
De forma genérica: randi() % (valor_maximo + 1 - valor_minimo) + valor_minimo
.
É comum arrendondar valor_maximo
para baixo (usando piso ou floor()
) e valor_minimo
para cima (usando teto ou ceil()
).
function inteiro_aleatorio(minimo_inclusive, maximo_inclusive) {
let minimo = Math.ceil(minimo_inclusive)
let maximo = Math.floor(maximo_inclusive)
return Math.floor(minimo + Math.random() * (maximo + 1 - minimo))
}
for (let i = 0; i < 10; ++i) {
// [0, 10]
let parte_inteira = inteiro_aleatorio(0, 10)
// [0.0, 1.0[
let parte_real = Math.random()
let numero = parte_inteira + parte_real
console.log(parte_inteira)
console.log(parte_real)
console.log(numero)
console.log("")
}
import random
random.seed()
for i in range(10):
# [0, 10]
parte_inteira = random.randint(0, 10)
# [0.0, 1.0[
parte_real = random.random()
numero = parte_inteira + parte_real
print(parte_inteira)
print(parte_real)
print(numero)
print("")
math.randomseed(os.time())
for i = 0, 9 do
-- [0, 10]
local parte_inteira = math.random(0, 10)
-- [0.0, 1.0[
local parte_real = math.random()
local numero = parte_inteira + parte_real
print(parte_inteira)
print(parte_real)
print(numero)
print("")
end
extends Node
func inteiro_aleatorio(minimo_inclusive, maximo_inclusive):
var minimo = ceil(minimo_inclusive)
var maximo = floor(maximo_inclusive)
# randi(): [0.0, 1.0[
return randi() % int(maximo + 1 - minimo) + minimo
func _ready():
randomize() # rand_seed(OS.get_unix_time()) / rand_seed(OS.get_system_time_secs())
for i in range(10):
# [0, 10]
var parte_inteira = inteiro_aleatorio(0, 10)
var parte_real = randf()
var numero = parte_inteira + parte_real # rand_range(0.0, 10.0)
print(parte_inteira)
print(parte_real)
print(numero)
print("")
A semente é usada para definir a seqüência gerada de números pseudoaleatórios. Caso se use sempre uma mesma semente, os valores sorteados serão sempre os mesmos. Uma forma prática de alterar a semente a cada uso do programa consiste em inicializá-la usando o horário atual da máquina. Algumas linguagens de programação fazem isso por padrão.
Sabendo-se sortear números, a criação de um programa para adivinhar um número é similar ao criado para adivinhar uma palavra. O ideal é sortear um número inteiro como segredo para o programa, para se evitar problemas de precisão. Para criar o programa, deve-se:
- Sortear número;
- Repetir:
- Solicitar a entrada de um palpite;
- Comparar o palpite com o número sorteado.
- Se o palpite estiver correto, o jogo termina com vitória.
- Se o palpite estiver incorreto, o programa retorna para o Passo 2.1. Opcionalmente, pode-se informar se o número sorteado é menor ou maior que o último palpite.
const MENOR_VALOR = 0
const MAIOR_VALOR = 100
function inteiro_aleatorio(minimo_inclusive, maximo_inclusive) {
let minimo = Math.ceil(minimo_inclusive)
let maximo = Math.floor(maximo_inclusive)
return Math.floor(minimo + Math.random() * (maximo + 1 - minimo))
}
let numero_secreto = inteiro_aleatorio(MENOR_VALOR, MAIOR_VALOR)
let menor_valor_possivel = MENOR_VALOR
let maior_valor_possivel = MAIOR_VALOR
let palpite = MENOR_VALOR - 1
while (palpite !== numero_secreto) {
palpite = parseInt(prompt("Escolha um número entre " + menor_valor_possivel + " e " + maior_valor_possivel + ":"))
if (palpite < numero_secreto) {
menor_valor_possivel = palpite
console.log("Número muito pequeno; escolha um maior.")
} else if (palpite > numero_secreto) {
maior_valor_possivel = palpite
console.log("Número muito grande; escolha um menor.")
}
}
console.log("Parabéns!")
import random
from typing import Final
MENOR_VALOR: Final = 0
MAIOR_VALOR: Final = 100
random.seed()
numero_secreto = random.randint(MENOR_VALOR, MAIOR_VALOR)
menor_valor_possivel = MENOR_VALOR
maior_valor_possivel = MAIOR_VALOR
palpite = MENOR_VALOR - 1
while (palpite != numero_secreto):
palpite = int(input("Escolha um número entre " + str(menor_valor_possivel) + " e " + str(maior_valor_possivel) + ": "))
if (palpite < numero_secreto):
menor_valor_possivel = palpite
print("Número muito pequeno; escolha um maior.")
elif (palpite > numero_secreto):
maior_valor_possivel = palpite
print("Número muito grande; escolha um menor.")
print("Parabéns!")
local MENOR_VALOR <const> = 0
local MAIOR_VALOR <const> = 100
math.randomseed(os.time())
local numero_secreto = math.random(MENOR_VALOR, MAIOR_VALOR)
local menor_valor_possivel = MENOR_VALOR
local maior_valor_possivel = MAIOR_VALOR
local palpite = MENOR_VALOR - 1
while (palpite ~= numero_secreto) do
print("Escolha um número entre " .. menor_valor_possivel .. " e " .. maior_valor_possivel .. ": ")
palpite = io.read("*number")
if (palpite < numero_secreto) then
menor_valor_possivel = palpite
print("Número muito pequeno; escolha um maior.")
elseif (palpite > numero_secreto) then
maior_valor_possivel = palpite
print("Número muito grande; escolha um menor.")
end
end
print("Parabéns!")
extends Node
const MENOR_VALOR = 0
const MAIOR_VALOR = 100
func inteiro_aleatorio(minimo_inclusive, maximo_inclusive):
var minimo = ceil(minimo_inclusive)
var maximo = floor(maximo_inclusive)
# randi(): [0.0, 1.0[
return randi() % int(maximo + 1 - minimo) + minimo
func _ready():
randomize()
var numero_secreto = inteiro_aleatorio(MENOR_VALOR, MAIOR_VALOR)
var menor_valor_possivel = MENOR_VALOR
var maior_valor_possivel = MAIOR_VALOR
var palpite = MENOR_VALOR - 1
while (palpite != numero_secreto):
print("Escolha um número entre " + str(menor_valor_possivel) + " e " + str(maior_valor_possivel) + ": ")
# Embora não seja possível ler um valor, é possível sortear um valor.
palpite = inteiro_aleatorio(menor_valor_possivel, maior_valor_possivel)
print(palpite)
if (palpite < numero_secreto):
menor_valor_possivel = palpite
print("Número muito pequeno; escolha um maior.")
elif (palpite > numero_secreto):
maior_valor_possivel = palpite
print("Número muito grande; escolha um menor.")
print("Parabéns!")
Para praticar, escreva quantas tentativas foram necessárias até acertar o número sorteado. Você também pode:
- Adicionar mensagens especiais para determinados números de tentativas;
- Impedir a entrada de valores que não sejam mais possíveis;
- Definir níveis de dificuldade.
Por exemplo:
- Fácil: 10 números;
- Médio: 100 números;
- Difícil: 1000 números;
- Tente a sua sorte: 100000000 números.
- Solicitar um valor mínimo e máximo para o sorteio.
Com uma boa estratégia e um programa que informe se o palpite é maior ou menor o valor secreto, você pode identificar o valor sorteado com poucos palpites.
Para isso, basta escolher sempre o valor médio do intervalo, adaptando a próxima escolha com o novo intervalo até acertar.
Por exemplo, a partida com 100000000 possibilidades pode levar cerca de tentativas no pior caso.
Usando o interpretador de JavaScript para fazer o cálculo Math.log2(100000000)
, isso significa que você levará, no máximo, 27 jogadas para acertar o valor.
Novos Itens para Seu Inventário
Ferramentas:
- Contador de desempenho e temporizador;
- Geradores de números pseudoaleatórios (PRNGs);
- Teste de mesa.
Habilidades:
- Depuração de programas usando teste de mesa;
- Criação de repetições;
- Uso de números aleatórios;
- Validação de entrada.
Conceitos:
- Repetições, iterações e laços;
- Estruturas de repetição;
- Repetição com teste no início;
- Repetição com teste no fim;
- Teste de mesa;
- Laço (loop) infinito;
- Acumulador;
- Interrompa e continue;
- Complexidade computacional;
- Número pseudoaleatórios;
- Código idiomático.
Recursos de programação:
- Estruturas de repetição;
- Números aleatórios;
- Temporizadores;
- Solução por força bruta (em Pratique).
Pratique
Para complementar as sugestões de modificações para os exemplos fornecidos, esta seção apresenta exercícios para a prática. Os exercícios de séries matemáticas mostram como calcular algumas constantes, funções e valores matemáticos.
Crie um programa que leia dez valores numéricos. Escreva o maior valor e o menor. Escreva também se ocorreu a leitura de um valor negativo.
Crie um programa que implemente a operação de potenciação para números com expoentes inteiros. Você pode definir o programa como uma função com assinatura
potencia(base: real, expoente: inteiro)
. Assuma queexpoente
seja um valor positivo ou zero. Por exemplo, para fazerpotencia(1.23, 3)
, deve-se realizar a operação1.23 * 1.23 * 1.23
. Lembre-se também quepotencia(x, 0)
resulta em 1 para qualquerx
diferente de zero.Some uma seqüência de números positivos fornecidas pelo(a) usuário(a) final. Termine a soma quando ele(a) fornecer o valor
-1
(ou qualquer outro número negativo). Escreva o resultado somado.Altere o programa anterior para somar números. Após cada novo número lido, leia uma cadeia de caracteres. Caso a cadeia de caracteres seja
resultado
, apresente o resultado. Caso a cadeia de caracteres sejasair
, termine o programa. Caso contrário, solicite um novo valor e continue o somatório.A série para calcular um número harmônico em Matemática pode ser definida pela seguinte expressão:
Escreva um programa que calcule , , , e . Diferentemente das outras séries apresentadas, a série harmônica não converge para um valor específico. Em outras palavras, o resultado continuará aumentando conforme o número de termos aumentar, aproximando-se (lentamente) de infinito.
Para aproximar o valor de , pode-se usar a seguinte série:
Escreva um programa para calcular o valor e compare o resultado com
Math.log(2)
em JavaScript. Caso não queira usar a chamada, o valor é, aproximadamente, 0.69314718.Para calcular o número de Euler, pode-se usar a seguinte série:
Como o denominador utiliza um fatorial, é conveniente usar um número relativamente pequeno para os termos para evitar overflow. Por exemplo, pode-se assumir o cálculo para dez termos. O resultado deverá ser próximo de
Math.E
em JavaScript (aproximadamente 2.7182818).Para calcular o cosseno de um número, pode-se usar a seguinte série:
Sendo
x
um ângulo em radianos. Por exemplo,cos(0)
é igual a1
. O cosseno de 30° ( rad) é, aproximadamente, 0.8660254037844387. Você pode testar os valores usando, por exemplo,Math.cos(Math.PI / 6.0)
em JavaScript.Caso facilite, você pode alterar o primeiro termo para escrevê-lo em função de zero.
Imagine que você queira criar um sistema para caixa de supermercados. Calcule o preço total de uma compra na qual, a cada passo, forneça-se como entradas:
- O valor unitário do produto;
- A quantidade de itens.
Caso queira sofisticar, você pode fornecer opções:
- Venda por item:
- O valor unitário do produto;
- A quantidade de itens.
- Venda por massa (peso):
- O preço do item por grama (R$/g);
- A massa do item em gramas.
Leia novos itens até que se forneça um valor negativo para valor unitário. Caso preferir, você pode determinar outra condição de parada para o laço.
Nas implementações de uma listagem de valores para fatorial como apresentada a seguir, é possível calcular o resultado sem a necessidade da repetição aninhada (bastaria a externa). Em outras palavras, a complexidade da solução poderia ser simplificada de quadrática para linear. Você consegue identificar como? Modifique o código com sua solução.
for (let numero = 0; numero < 11; ++numero) { let fatorial = 1 for (let multiplicador = 2; multiplicador <= numero; ++multiplicador) { fatorial *= multiplicador } console.log(numero, "! = ", fatorial) }
for numero in range(11): fatorial = 1 for multiplicador in range(2, numero + 1): fatorial *= multiplicador print(numero, "! = ", fatorial)
for numero = 0, 10 do local fatorial = 1 for multiplicador = 2, numero do fatorial = fatorial * multiplicador end print(numero, "! = ", fatorial) end
extends Node func _ready(): for numero in range(11): var fatorial = 1 for multiplicador in range(2, numero + 1): fatorial *= multiplicador print(numero, "! = ", fatorial)
Para calcular a média arimética de valores, pode-se usar a seguinte expressão:
Escreva um programa que leia 10 valores e escreva a média aritmética resultante. Em seguida, altere o programa para que ele continue lendo valores até a entrada de -1. Após o valor -1, calcule a média aritmética dos valores e apresente o resultado.
Escreva um programa que simule o lançamento de um número de dados fornecidos como entrada. Some os valores e apresente o resultado. Para sortear o valor de um dado, use uma função para obter um número aleatório entre 1 a 6.
Considere um programa que simule lançamentos de dados com um número de faces fornecida como entrada. Por exemplo, caso a entrada seja 20, sorteie números entre 1 e 20. Simule o sorteio de 1000 dados e forneça a média aritmética dos valores obtidos.
O cálculo do lucro de uma loja fictícia para um produto pode ser calculado pela seguinte expressão:
Considerando que seja o preço do produto, qual preço geraria o maior lucro, isto é, o maior valor para ? Assuma que . Escreva o maior lucro e o preço que gerou o maior lucro.
Embora seja possível obter a resposta matematicamente, também é possível obtê-la computacionalmente usando uma simulação. Calcule todos os valores do intervalo com incremento e armazene os resultados mais promissores. Esta técnica para solucionar problemas é conhecida em programação como força bruta (brute force), na qual se realiza uma busca exaustiva pelo resultado.
Nota. Algumas linguagens de programação permitem o uso do comando
para
apenas para valores inteiros. Por exemplo, em Python, pode-se usarenquanto
ao invés depara
para definir as repetições.Mostrar/Ocultar Solução
function l(x) { let x_2 = x * x let x_3 = x_2 * x let resultado = -0.012 * x_3 + 4.56 * x_2 + 77.7 * x + 1234.56 return resultado } let maior_lucro = l(0.01) let preco_maior_lucro = l(0.01) for (let preco = 0.02; preco < 1000.00; preco += 0.01) { let lucro = l(preco) if (lucro > maior_lucro) { maior_lucro = lucro preco_maior_lucro = preco } } console.log("Maior lucro: ", maior_lucro) console.log("Preço para maior lucro: ", preco_maior_lucro)
def l(x): x_2 = x * x x_3 = x_2 * x resultado = -0.012 * x_3 + 4.56 * x_2 + 77.7 * x + 1234.56 return resultado maior_lucro = l(0.01) preco_maior_lucro = l(0.01) preco = 0.02 while (preco <= 1000.00): lucro = l(preco) if (lucro > maior_lucro): maior_lucro = lucro preco_maior_lucro = preco preco += 0.01 print("Maior lucro: ", maior_lucro) print("Preço para maior lucro: ", preco_maior_lucro)
function l(x) local x_2 = x * x local x_3 = x_2 * x local resultado = -0.012 * x_3 + 4.56 * x_2 + 77.7 * x + 1234.56 return resultado end local maior_lucro = l(0.01) local preco_maior_lucro = l(0.01) for preco = 0.02, 1000.00, 0.1 do local lucro = l(preco) if (lucro > maior_lucro) then maior_lucro = lucro preco_maior_lucro = preco end preco = preco + 0.01 end print("Maior lucro: ", maior_lucro) print("Preço para maior lucro: ", preco_maior_lucro)
extends Node func l(x): var x_2 = x * x var x_3 = x_2 * x var resultado = -0.012 * x_3 + 4.56 * x_2 + 77.7 * x + 1234.56 return resultado func _ready(): var maior_lucro = l(0.01) var preco_maior_lucro = l(0.01) var preco = 0.02 while (preco <= 1000.00): var lucro = l(preco) if (lucro > maior_lucro): maior_lucro = lucro preco_maior_lucro = preco preco += 0.01 print("Maior lucro: ", maior_lucro) print("Preço para maior lucro: ", preco_maior_lucro)
Em estruturas de condição, um dos exercícios descrevia a implementação de um jogo Pedra, Papel e Tesoura.
Com números aleatórios, agora você pode sortear uma das jogadas. Por exemplo, pode-se sortear valores entre 0 a 2 (0, 1 e 2):
- 0 poderia ser pedra;
- 1 poderia ser papel;
- 2 poderia ter tesoura.
Você pode mudar os valores dos números conforme quiser. Modifique seu programa.
Para acrescentar estruturas de repetição, defina um campeonato do formato melhor de 3. A primeira pessoa (ou máquina) que ganhar duas vezes é declarada vencedora.
Próximos Passos
Ao contrário de pessoas, computadores nunca se cansam em repetir instruções. Eles são ótimas máquinas para calcular e repetir. Conhecendo ambas as operações, você pode utilizar boa parte do potencial das máquinas.
Assim, a introdução de estruturas de repetição representa um marco importante para este material. Com o que se apresentou até aqui, já é possível resolver muitos problemas usando programação. Tipos de dados, variáveis, operações aritméticas, lógicas e relacionais, entrada e saída, subrotinas, estruturas de condição e repetições são recursos fundamentais para a criação de programas. Muitos desses recursos estarão presentes em qualquer programa que você escrever.
Contudo, alguns problemas ainda não são possíveis de resolver, enquanto outros ainda poderiam ser mais convenientes com novos recursos.
Até este momento, cada variável pode armazenar um único valor. Por exemplo, o programa de adivinhar números pode armazenar o maior e o menor valor fornecidos, mas não todos eles. Embora seja possível declarar uma variável por valor, certamente não é algo conveniente.
Os próximos tópicos permitirão resolver problemas que envolvam mais dados de forma mais conveniente. Com estruturas de dados como vetores (arrays), listas e dicionários, uma única variável poderá mapear e armazenar múltiplos valores acessados por meio de uma chave.
- Introdução;
- Ponto de entrada e estrutura de programa;
- Saída (para console ou terminal);
- Tipos de dados;
- Variáveis e constantes;
- Entrada (para console ou terminal);
- Aritmética e Matemática básica;
- Operações relacionais e comparações;
- Operações lógicas e Álgebra Booleana;
- Estruturas de condição (ou condicionais ou de seleção);
- Subrotinas: funções e procedimentos;
- Estruturas de repetição (ou laços ou loops);
- Vetores (arrays), cadeias de caracteres (strings), coleções (collections) e estruturas de dados;
- Registros (structs ou records);
- Arquivos e serialização (serialization ou marshalling);
- Bibliotecas;
- Entrada em linha de comando;
- Operações bit-a-bit (bitwise operations);
- Testes e depuração.