Nim Básico
Nim Básico
Nim Básico
Original em inglês
A frase que você deseja imprimir deve seguir o comando echo e deve estar entre aspas
duplas ( " ).
Primeiro, precisamos compilar nosso programa e, em seguida, executá-lo para ver se funciona
conforme o esperado.
Abra seu terminal no mesmo diretório onde seu arquivo está (no Linux você pode obter 'Abrir
Terminal aqui' se clicar com o botão direito do mouse no diretório em seu gerenciador de
arquivos, no Windows você deve usar Shift + clique com o botão direito para obter a opção de
menu para Abrir a linha de comando).
Compilamos nosso programa digitando no terminal:
nim c helloworld.nim
Após uma compilação bem-sucedida, podemos executar nosso programa. No Linux, podemos
executar nosso programa digitando ./helloworld no terminal, e no Windows, digitando
helloworld.exe.
Também existe a possibilidade de compilar e executar o programa com apenas um comando.
Precisamos digitar:
nim c -r helloworld.nim
c está dizendo ao Nim para compilar o arquivo e -r está dizendo para executá-lo
imediatamente. Para ver todas as opções do compilador, digite nim --help em seu terminal.
Se você estiver usando o VSCode com a extensão Code Runner mencionada antes, você
apenas terá que pressionar Ctrl + Alt + N e seu arquivo será compilado e executado.
Qualquer que seja a forma que você escolheu para executar seu programa, após um breve
momento na janela de saída (ou em seu terminal), você verá:
Hello World!
Parabéns, você executou com sucesso seu primeiro programa Nim!
Agora você sabe como imprimir algumas coisas na tela (usando o comando echo), compilar
seu programa (digitando nim c nomedoprograma.nim em seu terminal) e executá-lo (várias
possibilidades).
Agora podemos começar a explorar os elementos básicos que nos ajudarão a escrever
programas Nim simples.
No exemplo pi = 3.14, o nome pi está conectado ao valor 3.14. Com base em nossa
experiência, podemos dizer que o tipo de variável pi é um número (decimal).
Outro exemplo seria firstName = Alice, onde firstName é o nome de uma variável com o
valor Alice. Diríamos que o tipo desta variável é uma palavra.
Em linguagens de programação, isso funciona de forma semelhante. Essas atribuições de
nome têm seu nome, o valor e um tipo.
Declaração de variável
Nim é uma linguagem de programação com tipagem estática, o que significa que o tipo de uma
atribuição precisa ser declarado antes de usar o valor.
Em Nim, também distinguimos valores que podem mudar, ou sofrer mutação, daqueles que
não podem, mas mais sobre isso mais tarde. Podemos declarar uma variável (uma atribuição
mutável) usando a palavra-chave var, apenas declarando seu nome e tipo (o valor pode ser
adicionado posteriormente) usando esta sintaxe:
Se já sabemos seu valor, podemos declarar uma variável e dar a ela um valor imediatamente:
Os colchetes angulares (<>) são usados para mostrar algo que você pode alterar. Portanto,
não é literalmente a palavra name entre colchetes angulares, mas qualquer nome.
Portanto, podemos atribuir uma variável sem um tipo explícito como este:
var <name> = <value>
var
c = -11
d = "Hello"
e = '!'
No Nim, as guias de indentação não são permitidas como recuo (tabulação). Você pode
configurar seu editor de código para converter o pressionamento de Tab em qualquer
número de espaços. No VS Code, a configuração padrão é converter Tab em quatro
espaços. Isso é facilmente substituído nas configurações (Ctrl +,) definindo 'editor.tabSize':
2.
Como as variáveis mencionadas anteriormente são mutáveis, ou seja, seu valor pode mudar
(várias vezes), mas seu tipo deve permanecer o mesmo que o declarado.
var f = 7 # (1)
f = -3 # (2)
f = 19 # (3)
f = "Hello" # error (4)
Atribuição imutável
Ao contrário das variáveis declaradas com a palavra-chave var, mais dois tipos de atribuição
existem em Nim, cujo valor não pode ser alterado, um declarado com a palavra-chave const e
o outro declarado com a palavra-chave let.
Const
O valor de uma atribuição imutável declarada com a palavra-chave const deve ser conhecido
em tempo de compilação (antes que o programa seja executado).
Por exemplo, podemos declarar a aceleração da gravidade como const g = 9.81 ou pi como
const pi = 3.14, pois sabemos seus valores de antemão e esses valores não mudarão durante
a execução de nosso programa.
const g = 35
g = -27 # error (1)
var h = -5
const i = h + 7 # error (2)
Let
Atribuições imutáveis declaradas com let não precisam ser conhecidas em tempo de
compilação, seu valor pode ser definido a qualquer momento durante a execução de um
programa, mas uma vez definido, seu valor não pode ser alterado.
let j = 35
j = -27 # error (1)
var k = -5
let l = k + 7 # (2)
Embora você possa usar var para tudo, sua escolha padrão deve ser let. Use var apenas para
as variáveis que serão modificadas.
Por exemplo: 32, -174, 0, 10_000_000 são todos inteiros. Observe que podemos usar _ como
um separador de milhares, para tornar os números maiores mais legíveis (é mais fácil ver que
estamos falando de 10 milhões quando é escrito como 10_000_000 em vez de 10000000).
A divisão inteira (divisão em que a parte fracionária é descartada) pode ser obtida com o
operador div. Um operador mod é usado se alguém estiver interessado no resto (módulo) de
uma divisão inteira. O resultado dessas duas operações é sempre um número inteiro.
integers.nim
let
a = 11
b = 4
a + b = 15
a - b = 7
a * b = 44
a / b = 2.75
a div b = 2
a mod b = 3
floats.nim
let
c = 6.75
d = 2.25
c + d = 9.0
c - d = 4.5
c * d = 15.1875
c / d = 3.0
Observe que nos exemplos de adição e divisão, embora obtenhamos um número sem uma
parte decimal, o resultado ainda é do tipo flutuante.
A precedência das operações matemáticas é como seria de esperar: multiplicação e divisão
têm prioridade mais alta do que adição e subtração.
echo 2 + 3 * 4
echo 24 - 8 / 4
14
22.0
let
e = 5
f = 23.456
echo e + f # error
Os valores das variáveis precisam ser convertidos para o mesmo tipo. A conversão é direta:
para converter para um inteiro, usamos a função int, e para converter para um float, a função
float é usada.
let
e = 5
f = 23.987
5.0
23
28.987
28
Ao usar a função int para converter um float em um inteiro, nenhum arredondamento será
executado. O número simplesmente elimina quaisquer casas decimais. Para realizar o
arredondamento devemos chamar outra função, mas para isso devemos saber um pouco
mais sobre como usar o Nim.
Characters
O tipo char é usado para representar um único caractere ASCII.
Os caracteres são escritos entre duas aspas simples ( ' ). Os caracteres podem ser letras,
símbolos ou dígitos únicos. Vários dígitos ou várias letras produzem um erro.
let
h = 'z'
i = '+'
j = '2'
k = '35' # error
l = 'xy' # error
Strings
Strings podem ser descritos como uma série de caracteres. Seu conteúdo é escrito entre duas
aspas duplas ( " ).
Podemos pensar em strings como palavras, mas elas podem conter mais de uma palavra,
alguns símbolos ou dígitos.
let
m = "word"
n = "A sentence with interpunction."
o = "" # (1)
p = "32" # (2)
q = "!" # (3)
Caracteres especiais
Se tentarmos imprimir a seguinte string:
echo "some\nim\tips"
Isso ocorre porque existem vários caracteres que têm um significado especial. Eles são
usados colocando o caractere de escape \ antes deles.
\ n é um caractere de nova linha
\ t é um caractere de tabulação
\\ é uma barra invertida (uma vez que \ é usado como o caractere de escape)
Se quisermos imprimir o exemplo acima como foi escrito, temos duas possibilidades:
Use \\ em vez de \ para imprimir barras invertidas ou
Use strings brutas com sintaxe r '...' (colocando uma letra r imediatamente antes da
primeira aspa), nas quais não haja caracteres de escape e nenhum significado especial:
tudo é impresso como está.
echo "some\\nim\\tips"
echo r"some\nim\tips"
some\nim\tips
some\nim\tips
Existem mais caracteres especiais do que os listados acima, e todos eles são encontrados no
manual do Nim.
Concatenação de string
Strings em Nim são mutáveis, o que significa que seu conteúdo pode mudar. Com a função
add, podemos adicionar (anexar) outra string ou um caractere a uma string existente. Se não
quisermos alterar a string original, também podemos concatenar (juntar) strings com o
operador &, isso retorna uma nova string.
stringConcat.nim
var # (1)
p = "abc"
q = "xy"
r = 'z'
p.add("def") # (2)
echo "p is now: ", p
q.add(r) # (3)
echo "q is now: ", q
p is now: abcdef
q is now: xyz
concat: abcdefxyz
p is still: abcdef
q is still: xyz
Boleano
Um tipo de dado boolean (ou apenas bool) pode ter apenas dois valores: true ou false. Os
booleanos são geralmente usados para controlar o fluxo (consulte o próximo capítulo) e
geralmente são o resultado de operadores relacionais.
Operadores relacionais
Os operadores relacionais testam a relação entre duas entidades, que devem ser
comparáveis.
Para comparar se dois valores são iguais, == (dois sinais de igual) é usado. Não confunda com
=, que é usado para atribuição como vimos anteriormente.
Aqui estão todos os operadores relacionais definidos para inteiros:
relationalOperators.nim
let
g = 31
h = 99
let
i = 'a'
j = 'd'
k = 'Z'
echo i < j
echo i < k # (1)
let
m = "axyb"
n = "axyz"
o = "ba"
p = "ba "
true
false
true
true
true
Operadores lógicos
Operadores lógicos são usados para testar a veracidade de uma expressão que consiste em
um ou mais valores booleanos.
Lógico and retorna verdadeiro apenas se ambos os membros forem verdadeiros
Lógico or retorna verdadeiro se houver pelo menos um membro verdadeiro
O xor lógico retorna verdadeiro se um membro for verdadeiro, mas o outro não
O lógico not nega a veracidade de seu membro: mudando verdadeiro para falso e vice-
versa (é o único operador lógico que leva apenas um operando)
logicalOperators.nim
Operadores relacionais e lógicos podem ser combinados para formar expressões mais
complexas.
Por exemplo: (5 < 7) and (11 + 9 == 32 - 2 \ 6)* se tornará verdadeiro e (20 == 20), que se
tornará verdadeiro e verdadeiro, e no final dará o resultado final verdadeiro.
Recapitular
Este foi o capítulo mais longo deste tutorial e cobrimos muito terreno. Reserve um tempo para
examinar cada tipo de dados e experimente o que você pode fazer com cada um deles.
Os tipos podem parecer uma restrição no início, mas permitem que o compilador Nim torne
seu código mais rápido e certifique-se de que você não esteja fazendo algo errado por
acidente - isso é especialmente benéfico em grandes bases de código.
Agora você conhece os tipos de dados básicos e várias operações sobre eles, o que deve ser
suficiente para fazer alguns cálculos simples no Nim. Teste seus conhecimentos fazendo os
seguintes exercícios.
Exercícios
1. Crie uma variável imutável contendo sua idade (em anos). Imprima sua idade em dias. (1
ano = 365 dias)
2. Verifique se sua idade é divisível por 3. (Dica: use o mod)
3. Crie uma variável imutável contendo sua altura em centímetros. Imprima sua altura em
polegadas. (1 pol = 2,54 cm)
4. Um tubo tem 3/8 de polegada de diâmetro. Expresse o diâmetro em centímetros.
5. Crie uma variável imutável contendo seu primeiro nome e outra contendo seu sobrenome.
Faça uma variável fullName concatenando as duas variáveis anteriores. Não se esqueça
de colocar um espaço em branco no meio. Imprima seu nome completo.
6. Alice ganha R$400 a cada 15 dias. Bob ganha R$3,14 por hora e trabalha 8 horas por dia,
7 dias por semana. Depois de 30 dias, Alice ganhou mais do que Bob? (Dica: use
operadores relacionais)
Controle de uxo
Até agora, em nossos programas, cada linha de código foi executada em algum ponto. As
declarações de fluxo de controle nos permitem ter partes de código que serão executadas
apenas se alguma condição booleana for satisfeita.
Se pensarmos em nosso programa como uma estrada, podemos pensar no fluxo de controle
como vários ramos, e escolhemos nosso caminho dependendo de alguma condição. Por
exemplo, compraremos ovos apenas se seu preço for inferior a algum valor. Ou, se estiver
chovendo, levaremos um guarda-chuva, caso contrário (else) levaremos óculos escuros.
Escritos em pseudocódigo, esses dois exemplos seriam assim:
if estaChovendo:
traga quarda-chuva
else:
traga óculos escuros
Declaração If
Uma instrução if, conforme mostrado acima, é a maneira mais simples de ramificar nosso
programa.
if <condition>: # (1)
<indented block> # (2)
1. A condição deve ser do tipo booleano: uma variável booleana ou uma expressão relacional
and / or lógico.
2. Todas as linhas após a linha if que são indentadas com dois espaços formam o mesmo
bloco e serão executadas apenas se a condição for verdadeira.
As instruções if podem ser aninhadas, ou seja, dentro de um bloco if, pode haver outra
instrução if.
if.nim
let
a = 11
b = 22
c = 999
if a < b:
echo "a is smaller than b"
if 10*a < b: # (1)
echo "not only that, a is *much* smaller than b"
if b < c:
echo "b is smaller than c"
if 10*b < c: # (2)
echo "not only that, b is *much* smaller than c"
a is smaller than b
b is smaller than c
not only that, b is *much* smaller than c
Else
Else segue após um bloco if e nos permite ter uma ramificação do código que será executada
quando a condição na instrução if não for verdadeira.
else.nim
let
d = 63
e = 2.718
if d < 10:
echo "d is a small number"
else:
echo "d is a large number"
if e < 10:
echo "e is a small number"
else:
echo "e is a large number"
d is a large number
e is a small number
Se você deseja executar um bloco apenas se a declaração for falsa, você pode
simplesmente negar a condição com o operador not.
Elif
Elif é a abreviatura de 'else if' e nos permite encadear várias instruções if.
O programa testa cada afirmação até encontrar uma que seja verdadeira. Depois disso, todas
as outras instruções são ignoradas.
elif.nim
let
f = 3456
g = 7
if f < 10:
echo "f is smaller than 10"
elif f < 100:
echo "f is between 10 and 100"
elif f < 1000:
echo "f is between 100 and 1000"
else:
echo "f is larger than 1000"
if g < 1000:
echo "g is smaller than 1000"
elif g < 100:
echo "g is smaller than 100"
elif g < 10:
echo "g is smaller than 10"
if x == 5:
echo "Five!"
elif x == 7:
echo "Seven!"
elif x == 10:
echo "Ten!"
else:
echo "unknown number"
case x
of 5:
echo "Five!"
of 7:
echo "Seven!"
of 10:
echo "Ten!"
else:
echo "unknown number"
Ao contrário da instrução if, a instrução case deve cobrir todos os casos possíveis. Se alguém
não estiver interessado em algum desses casos, então: descarte pode ser usado.
case.nim
let h = 'y'
case h
of 'x':
echo "You've chosen x"
of 'y':
echo "You've chosen y"
of 'z':
echo "You've chosen z"
else: discard # (1)
1. Mesmo que estejamos interessados em apenas três valores de h, devemos incluir esta
linha para cobrir todos os outros casos possíveis (todos os outros caracteres). Sem ele, o
código não seria compilado.
You've chosen y
Também podemos usar vários valores para cada ramificação se a mesma ação ocorrer para
mais de um valor.
multipleCase.nim
let i = 7
case i
of 0:
echo "i is zero"
of 1, 3, 5, 7, 9:
echo "i is odd"
of 2, 4, 6, 8:
echo "i is even"
else:
echo "i is too large"
i is odd
Loops
Os loops são outra construção de fluxo de controle que nos permite executar algumas partes
do código várias vezes.
Loop for
Todas as linhas no corpo do loop são executadas em cada loop, o que nos permite escrever
com eficiência partes repetitivas do código.
for1.nim
for n in 5 .. 9: # (1)
echo n
echo ""
5
6
7
8
9
5
6
7
8
Se quisermos iterar por meio de um intervalo de números com um tamanho de passo diferente
de um, a contagem é usada. Com a contagem, definimos o valor inicial, o valor de parada
(incluído no intervalo) e o tamanho do passo.
for2.nim
1. Contando de zero a 16, com um tamanho de etapa de 4. O final (16) está incluído na faixa.
0
4
8
12
16
Para iterar por um intervalo de números onde o início é maior do que o final, uma função
semelhante chamada contagem regressiva é usada. Mesmo que estejamos em contagem
regressiva, o tamanho do passo deve ser positivo.
for2.nim
echo ""
1. Para iterar de um número superior para um número inferior, devemos usar a contagem
regressiva (o operador .. só pode ser usado quando o valor inicial é menor que o valor
final).
2. Mesmo durante a contagem regressiva, o tamanho do passo deve ser um número positivo.
4
3
2
1
0
-3
-5
-7
-9
Como a string é iterável, podemos usar um loop for para iterar por meio de cada caractere da
string (esse tipo de iteração às vezes é chamado de loop for-each).
for3.nim
for3.nim
letter 0 is: a
letter 1 is: l
letter 2 is: p
letter 3 is: h
letter 4 is: a
letter 5 is: b
letter 6 is: e
letter 7 is: t
Loop while
Os loops while são semelhantes às instruções if, mas eles continuam executando seu bloco
de código enquanto a condição permanecer verdadeira. Eles são usados quando não sabemos
com antecedência quantas vezes o loop será executado.
Devemos ter certeza de que o loop terminará em algum ponto e não se tornará um loop
infinito.
while.nim
var a = 1
a is: 1
a is: 2
a is: 3
final value of a: 4
Break e continue
A instrução break é usada para sair prematuramente de um loop, geralmente se alguma
condição for atendida.
No próximo exemplo, se não houvesse uma instrução if com break, o loop continuaria a ser
executado e impresso até que i se tornasse 1000. Com a instrução break, quando i chegar a
3, saímos imediatamente do loop (antes de imprimir o valor de i).
break.nim
var i = 1
1
2
continue.nim
for i in 1 .. 8:
if (i == 3) or (i == 6):
continue
echo i
1
2
4
5
7
8
Exercícios
1. A conjectura de Collatz é um problema matemático popular com regras simples. Primeiro
escolha um número. Se for ímpar, multiplique por três e some um; Se for par, divida por
dois. Repita este procedimento até chegar a um. Por exemplo. 5 → ímpar → 3 5 + 1 = 16
→ par → 16/2 = 8 → par → 4 → 2 → 1 → fim! Escolha um inteiro (como uma variável
mutável) e crie um loop que imprimirá todas as etapas da conjectura de Collatz. (Dica: use
**div* para divisão)
2. Crie uma variável imutável contendo seu nome completo. Escreva um laço for que irá iterar
através daquela string e imprimir apenas as vogais (a, e, i, o, u). (Dica: use a instrução
case com vários valores por ramo)
3. Fizz buzz é um jogo infantil às vezes usado para testar conhecimentos básicos de
programação. Contamos os números de um para cima. Se um número for divisível por 3,
substitua-o por fizz, se for divisível por 5 substitua-o por buzz e se um número for divisível
por 15 (ambos 3 e 5) substitua-o por fizzbuzz. As primeiras rodadas ficariam assim: 1, 2,
fizz, 4, buzz, fizz, 7, ... Crie um programa que irá imprimir as primeiras 30 rodadas de Fizz
Buzz. (Dica: cuidado com a ordem dos testes de divisibilidade)
4. Nos exercícios anteriores, você converteu polegadas em centímetros e vice-versa. Crie
uma tabela de conversão com vários valores. Por exemplo, a tabela pode ser assim:
in | cm
----------------
1 | 2.54
4 | 10.16
7 | 17.78
10 | 25.4
13 | 33.02
16 | 40.64
19 | 48.26
Containers
Os contêineres são tipos de dados que contêm uma coleção de itens e nos permitem acessar
esses elementos. Normalmente, um contêiner também é iterável, o que significa que podemos
usá-los da mesma forma que usamos strings no capítulo de loops.
Por exemplo, uma lista de compras é um contêiner de itens que queremos comprar, e uma lista
de números primos é um contêiner de números. Escrito em pseudocódigo:
Arrays
Uma matriz é o tipo de contêiner mais simples. As matrizes são homogêneas, ou seja, todos
os elementos em uma matriz devem ter o mesmo tipo. Os arrays também têm um tamanho
constante, o que significa que a quantidade de elementos (ou melhor: a quantidade de
elementos possíveis) deve ser conhecida em tempo de compilação. Isso significa que
chamamos arrays de 'contêiner homogêneo de comprimento constante'.
O tipo de array é declarado usando array [comprimento, tipo], onde comprimento é a
capacidade total do array (número de elementos que pode caber) e tipo é um tipo de todos os
seus elementos. A declaração pode ser omitida se o comprimento e o tipo puderem ser
inferidos dos elementos passados.
var
a: array[3, int] = [5, 7, 9]
b = [5, 7, 9] # (1)
c = [] # error (2)
d: array[7, string] # (3)
const m = 3
let n = 5
1. Isso produz um erro porque n é declarado usando let - seu valor não é conhecido em
tempo de compilação. Só podemos usar valores declarados com const como um parâmetro
de comprimento para uma inicialização de array.
Sequences
Sequências são contêineres semelhantes a arrays, mas seu comprimento não precisa ser
conhecido no tempo de compilação e pode mudar durante o tempo de execução: declaramos
apenas o tipo dos elementos contidos com seq [type]. As sequências também são
homogêneas, ou seja, cada elemento em uma sequência deve ser do mesmo tipo.
Os elementos de uma sequência são colocados entre @ [ e ].
var
e1: seq[int] = @[] # (1)
f = @["abc", "def"] # (2)
var
e = newSeq[int]() # (1)
1. Fornecer o parâmetro de tipo entre colchetes permite que o procedimento saiba que deve
retornar uma sequência de um determinado tipo. Um erro frequente é a omissão do final (),
que deve ser incluído.
Podemos adicionar novos elementos a uma sequência com a função add, semelhante ao que
fizemos com strings. Para que isso funcione, a sequência deve ser mutável (definida com var)
e o elemento que estamos adicionando deve ser do mesmo tipo que os elementos da
sequência.
seq.nim
var
g = @['x', 'y']
h = @['1', '2', '3']
g.add('z') # (1)
echo g
h.add(g) # (2)
echo h
var i = @[9, 8, 7]
i.add(9.81) # error (1)
g.add(i) # error (2)
var i = @[9, 8, 7]
echo i.len
i.add(6)
echo i.len
3
4
Se quisermos indexar "para trás", isso é feito usando o prefixo ^ . O último elemento (primeiro
na parte de trás) tem o índice ^ 1 .
indexing.nim
b
e
O fatiamento nos permite obter uma série de elementos com uma chamada. Ele usa a mesma
sintaxe dos intervalos (introduzidos na seção de loop for).
Se usarmos a sintaxe start .. stop, ambas as extremidades serão incluídas na fatia. Usando a
sintaxe start ..< stop, o índice de parada não é incluído na fatia.
A sintaxe para fatiar é <container>[<start> .. <stop>] .
indexing.nim
echo j[0 .. 3]
echo j[0 ..< 3]
@[a, b, c, d]
@[a, b, c]
Tanto a indexação quanto o fracionamento podem ser usados para atribuir novos valores aos
contêineres e strings mutáveis existentes.
assign.nim
var
k: array[5, int]
l = @['p', 'w', 'r']
m = "Tom and Jerry"
for i in 0 .. 4: # (1)
k[i] = 7 * i
echo k
1. A matriz de comprimento 5 possui índices de zero a quatro. Iremos atribuir um valor a cada
elemento do array.
2. Atribuir (alterar) o segundo elemento (índice 1) de uma sequência.
3. Alterar caracteres de uma string nos índices 8 e 9.
Tuplas
Ambos os contêineres que vimos até agora são homogêneos. Por outro lado, as tuplas contêm
dados heterogêneos, ou seja, os elementos de uma tupla podem ser de tipos diferentes. Da
mesma forma que as matrizes, as tuplas têm tamanho fixo.
1. As tuplas podem conter campos de diferentes tipos. Neste caso: string, int e char.
Também podemos nomear cada campo em uma tupla para distingui-los. Isso pode ser usado
para acessar os elementos da tupla, em vez de indexar.
tuples.nim
o[1] = 7 # (1)
o.name = "Apple" # (2)
echo o
Exercícios
1. Crie uma matriz vazia que pode conter dez inteiros.
Preencha essa matriz com números 10, 20, ..., 100. (Dica: use loops)
Imprima apenas os elementos dessa matriz que estão em índices ímpares (valores 20,
40,…).
Multiplique os elementos em índices pares por 5. Imprima a matriz modificada.
2. Refaça o exercício de conjectura de Collatz, mas desta vez, em vez de imprimir cada
passo, adicione-o a uma sequência.
Escolha um número inicial. As escolhas interessantes, entre outras, são 9, 19, 25 e 27.
Crie uma sequência cujo único membro seja aquele número inicial
Usando a mesma lógica de antes, continue adicionando elementos à sequência até
chegar a 1
Imprima o comprimento da sequência e a própria sequência
3. Encontre o número em um intervalo de 2 a 100 que produzirá a sequência de Collatz mais
longa.
Para cada número no intervalo determinado, calcule sua sequência de Collatz
Se o comprimento da sequência atual for maior do que o registro anterior, salve o
comprimento atual e o número inicial como um novo registro (você pode usar a tupla
(longestLength, StartingNumber) ou duas variáveis separadas)
Imprime o número inicial que fornece a sequência mais longa e seu comprimento
Procedures
Os procedimentos, ou funções como são chamados em algumas outras linguagens de
programação, são partes do código que executam uma tarefa específica, empacotados como
uma unidade. A vantagem de agrupar o código assim é que podemos chamar esses
procedimentos em vez de escrever todo o código novamente quando quisermos usar o código
do procedimento.
Declarando um procedure
Antes de podermos usar (chamar) nosso procedimento, precisamos criá-lo e definir o que ele
faz.
Um procedimento é declarado usando a palavra-chave proc e o nome do procedimento,
seguido pelos parâmetros de entrada e seu tipo entre parênteses, e a última parte são dois
pontos e o tipo do valor retornado de um procedimento, como este:
var ourVariable = 10
changeArgument(ourVariable)
Para que isso funcione, precisamos permitir que Nim e o programador usando nosso
procedimento alterem o argumento declarando-o como uma variável:
1. Observe como o argumento agora é declarado como var int e não apenas como int.
Isso, é claro, significa que o nome que passamos deve ser declarado também como uma
variável, passando algo atribuído com const ou let gerará um erro.
Embora seja uma boa prática passar as coisas como argumentos, também é possível usar
nomes declarados fora do procedimento, tanto variáveis quanto constantes:
var x = 100
proc echoX() =
echo x # (1)
x += 1 # (2)
echoX()
echoX()
100
101
Chamando os procedimentos
Depois de declarar um procedimento, podemos chamá-lo. A maneira usual de chamar
procedimentos / funções em muitas linguagens de programação é declarar seu nome e
fornecer os argumentos entre parênteses, como este:
let
a = findMax(987, 789)
b = findMax(123, 321)
c = findMax(a, b) # (1)
echo a
echo b
echo c
987
321
987
Esta é uma chamada em que o primeiro argumento é escrito antes do nome da função e o
resto dos parâmetros são declarados entre parênteses:
<arg1>.<procName>(<arg2>, ...)
Vimos esse estilo sendo usado quando chamamos o procedimento echo e ao chamar o
procedimento len sem nenhum argumento. Esses dois também podem ser combinados dessa
forma, mas essa sintaxe, entretanto, não é vista com muita frequência:
1. Se vários parâmetros forem do mesmo tipo, podemos declarar seu tipo desta forma
compacta.
2. Primeiro adicionamos a e b, então o resultado dessa operação (2 + 3 = 5) é passado como
o primeiro parâmetro para o procedimento multi, onde é multiplicado por c (5 \ 4 = 20*).
3. Primeiro, multiplicamos c e b, então o resultado dessa operação (4 \ 3 = 12*) é passado
como o primeiro parâmetro para o procedimento plus, onde é adicionado com a (12 + 2 =
14).
true
true
20
14
Variável de resultado
No Nim, todo procedimento que retorna um valor tem uma variável de resultado declarada
implicitamente e inicializada (com um valor padrão). O procedimento retornará o valor desta
variável de resultado quando atingir o final de seu bloco indentado, mesmo sem declaração de
retorno.
result.nim
1. O tipo de retorno é int. A variável result é inicializada com o valor padrão para int: 0.
2. Quando o final do procedimento é alcançado, o valor do result é retornado.
33
Observe que este procedimento serve para demonstrar a variável result, e não está 100%
correto: se você passasse uma sequência contendo apenas números negativos, este
procedimento retornaria 0 (que não está contido na sequência).
Cuidado! Em versões mais antigas do Nim (antes do Nim 0.19.0), o valor padrão de
strings e sequências era nulo, e quando íamos usá-los como tipos de retorno, a variável
result precisaria ser inicializada como uma string vazia ("") ou como uma sequência vazia
(@ []).
result.nim
1. Na versão Nim 0.19.0 e mais recente, esta linha não é necessária - as sequências são
inicializadas automaticamente como sequências vazias. Em versões mais antigas do Nim,
as sequências devem ser inicializadas e, sem essa linha, o compilador geraria um erro.
(Observe que var não deve ser usado, pois o resultado já está declarado implicitamente.)
filterOdds.nim
let
g = @[2, 6, 5, 7, 9, 0, 5, 3]
h = @[5, 4, 3, 2, 1]
i = @[626, 45390, 3219, 4210, 4126]
echo filterMultiplesOf3(g)
echo h.filterMultiplesOf3()
echo filterMultiplesOf3 i # (3)
1. Mais uma vez, esta linha não é necessária nas versões mais recentes do Nim.
2. Chamando o procedimento declarado anteriormente. Seu tipo de retorno é bool e pode ser
usado na instrução if.
3. A terceira forma de chamar um procedimento, como vimos acima.
@[6, 9, 0, 3]
@[3]
@[45390, 3219]
Declaração de encaminhamento
Conforme mencionado no início desta seção, podemos declarar um procedimento sem um
bloco de código. A razão para isso é que temos que declarar os procedimentos antes de
podermos chamá-los, fazer isso não funcionará:
1. Isso gerará um erro, pois o procedimento plus ainda não foi definido.
2. Aqui nós definimos plus, mas como é depois de usá-lo, Nim ainda não sabe sobre isso.
A maneira de contornar isso é chamada de declaração de encaminhamento:
1. Aqui, dizemos a Nim que ele deve considerar que o procedimento plus existe com essa
definição.
2. Agora estamos livres para usá-lo em nosso código, isso vai funcionar.
3. Isso é onde o plus é realmente implementado, isso deve corresponder à nossa definição
anterior.
Exercícios
1. Crie um procedimento que cumprimente uma pessoa ( imprima "Olá, <nome>" ) com
base no nome fornecido. Crie uma sequência de nomes. Cumprimente cada pessoa
usando o procedimento criado.
2. Crie um procedimento findMax3 que retornará o maior dos três valores.
3. Os pontos no plano 2D podem ser representados como tupla [x, y: float]. Escreva um
procedimento que receberá dois pontos e retornará um novo ponto que é a soma desses
dois pontos (adicione x e y separadamente).
4. Crie dois procedimentos tick e tock que ecoam as palavras 'tick' e 'tock'. Tenha uma
variável global para controlar quantas vezes eles foram executados, e execute um do outro
até que o contador alcance 20. A saída esperada é obter linhas com 'tick' e 'tock'
alternando 20 vezes. (Dica: use declarações de encaminhamento.)
Módulos
Até agora, usamos a funcionalidade que está disponível por padrão sempre que iniciamos um
novo arquivo Nim. Isso pode ser estendido com módulos, que fornecem mais funcionalidade
para algum tópico específico.
Alguns dos módulos Nim mais usados são:
strutils: funcionalidade adicional ao lidar com strings
sequtils: funcionalidade adicional para sequências
math: funções matemáticas (logaritmos, raízes quadradas, ...), trigonometria (sen, cos, ...)
times: medir e lidar com o tempo
Mas há muitos mais, tanto na chamada biblioteca padrão quanto no gerenciador de pacotes
ágil.
Importando um módulo
Se quisermos importar um módulo e todas as suas funcionalidades, basta colocar import \ em
nosso arquivo. Isso geralmente é feito na parte superior do arquivo para que possamos ver
facilmente o que nosso código usa.
stringutils.nim
let
a = "My string with whitespace."
b = '!'
maths.nim
let
c = 30.0 # degrees
cRadians = c.degToRad() # (2)
echo cRadians
echo sin(cRadians).round(2) # (3)
1. Importando math.
2. Convertendo graus em radianos com degToRad.
3. O seno recebe radianos. Arredondamos (também a partir do módulo de math) o resultado
para no máximo 2 casas decimais. (Caso contrário, o resultado seria:
0.4999999999999999)
4. O módulo math também possui o operador ^ para calcular as potências de um número.
0.5235987755982988
0.5
32
1. Observe como o procedimento de adição agora tem um asterisco (*) após seu nome, isso
informa ao Nim que outro arquivo importando este poderá usar este procedimento.
2. Por outro lado, isso não estará visível ao importar este arquivo.
secondFile.nim
.
├── myOtherSubdir
│ ├── fifthFile.nim
│ └── fourthFile.nim
├── mySubdir
│ └── thirdFile.nim
├── firstFile.nim
└── secondFile.nim
Se você quiser importar todos os outros arquivos em seu secondFile.nim, faça o seguinte:
secondFile.nim
import firstFile
import mySubdir/thirdFile
import myOtherSubdir / [fourthFile, fifthFile]
Lendo de um arquivo
Digamos que temos um arquivo de texto chamado people.txt no mesmo diretório que nosso
código Nim. O conteúdo desse arquivo é parecido com este:
people.txt
Alice A.
Bob B.
Carol C.
readFromFile.nim
import strutils
Alice A.
Bob B.
Carol C.
# (1)
@["Alice A.", "Bob B.", "Carol C.", ""] # (2)
1. Havia uma nova linha final (última linha vazia) no arquivo original, que também está
presente aqui.
2. Por causa da nova linha final, nossa sequência é mais longa do que esperávamos.
Para resolver o problema de uma nova linha final, podemos usar o procedimento de tira de
strutils depois de ler um arquivo. Tudo o que isso faz é remover os chamados espaços em
branco do início e do final de nossa string. O espaço em branco é simplesmente qualquer
caractere que crie algum espaço, novas linhas, espaços, tabulações, etc.
readFromFile2.nim
import strutils
Alice A.
Bob B.
Carol C.
@["Alice A.", "Bob B.", "Carol C."]
import strutils
1. Converta uma string em um inteiro. Quando escrito assim, confiamos em nosso usuário
para fornecer um número inteiro válido. O que aconteceria se um usuário inserisse '79 ou
noventa e três? Tente você mesmo.
Se tivermos o arquivo numbers.txt no mesmo diretório que nosso código Nim, com o seguinte
conteúdo:
numbers.txt
27.3
98.24
11.93
33.67
55.01
E queremos ler esse arquivo e encontrar a soma e a média dos números fornecidos, podemos
fazer algo assim:
interaction3.nim
let
strNums = readFile("numbers.txt").strip().splitLines() # (2)
nums = strNums.map(parseFloat) # (3)
let
sumNums = sum(nums) # (4)
average = sumNums / float(nums.len) # (5)
echo sumNums
echo average
1. Importamos vários módulos. strutils fornece strip e splitLines, sequtils fornece map e
math fornece sum.
2. Retiramos a nova linha final e dividimos as linhas para criar uma sequência de strings.
3. map funciona aplicando um procedimento (neste caso, parseFloat) a cada membro de um
contêiner. Em outras palavras, convertemos cada string em um float, retornando uma
nova sequência de floats.
4. Usando sum do módulo math para nos dar a soma de todos os elementos em uma
sequência.
5. Precisamos converter o comprimento de uma sequência em float, porque sumNums é um
float.
226.15
45.23
Exercícios
1. Pergunte a um usuário sua altura e peso. Calcule seu IMC. Relate-lhes o valor do IMC e a
categoria.
2. Repita o exercício de conjectura de Collatz para que seu programa peça a um usuário um
número inicial. Imprima a sequência resultante.
3. Peça a um usuário uma string que ele deseja que seja invertida. Crie um procedimento que
recebe uma string e retorna uma versão reversa. Por exemplo, se o usuário digitar Nim-
lang, o procedimento deve retornar gnal-miN. (Dica: use indexação e contagem regressiva)
Conclusão
É hora de concluir este tutorial. Esperançosamente, isso foi útil para você e você conseguiu
dar os primeiros passos na programação e/ou na linguagem de programação Nim.
Estes têm sido apenas o básico e nós apenas arranhamos a superfície, mas isso deve ser o
suficiente para permitir que você faça programas simples e resolva algumas tarefas ou quebra-
cabeças simples. Nim tem muito mais a oferecer e, com sorte, você continuará a explorar suas
possibilidades.