Em programação, a iteração refere-se ao processo de repetir um bloco de código várias vezes. Isto pode ser feito com conceitos como ciclos while e ciclos until. Também pode ser feito com recursão, que é uma função que se chama a si própria.
No entanto, na maioria das vezes queres iterar sobre uma coleção de itens, como um Array, um Range ou uma String.
Tanto a classe Array como a classe Range em Crystal incluem o módulo Enumerable, que fornece vários métodos para iterar sobre os elementos.
Entretanto, a classe String tem o seu próprio conjunto de métodos para iterar sobre os carateres.
Crystal também não tem nenhuma instrução for como outras linguagens. Em vez disso, tem vários métodos que podes usar para iterar.
A forma mais comum de iterar sobre uma coleção é usar o método each, que passa cada elemento da coleção a um bloco.
Isto faz-se facilmente com um Range.
Imagina que queres fazer um ciclo entre 1 e 3: podes usar o método each para iterar sobre o intervalo.
(1..3).each do |n|
puts n
end
# Output:
# 1
# 2
# 3
Ainda mais simples: se só quiseres iterar um certo número de vezes, podes usar o método times, que existe na classe Int.
3.times do |n|
puts n
end
# Output:
# 0
# 1
# 2
String
Uma String é uma sequência de carateres e não pertence ao módulo Enumerable, o que significa que tem o seu próprio conjunto de métodos para iterar sobre os carateres.
A forma mais comum de iterar sobre uma String é usar o método each_char, que passa cada caráter da String a um bloco.
O método each_char passa um objeto Char ao bloco e não um objeto String.
str = "hello"
str.each_char { |char| puts char }
# Output:
# h
# e
# l
# l
# o
Outra forma de iterar sobre uma String é usar o método each_line.
Este método é usado sobretudo quando se lê um ficheiro linha a linha.
str = "hello\nworld"
str.each_line do |line|
puts line
end
# Output:
# hello
# world
Enumerable
O módulo Enumerable fornece vários métodos para iterar sobre os elementos de uma coleção.
As coleções que incluem o módulo Enumerable são Array, Range, Hash, Set, entre outras; estas últimas serão abordadas em conceitos posteriores.
A forma mais comum de iterar sobre um Array é usar o método each, que passa cada elemento do Array a um bloco.
arr = [5, 2, 3]
arr.each do |element|
puts element
end
# Output:
# 5
# 2
# 3
O método map é outra forma de iterar sobre um Array; devolve um novo Array que contém os resultados de aplicar o bloco a cada elemento.
É semelhante ao método each, mas devolve um novo Array com os elementos transformados.
A transformação é muito útil quando queres aplicar uma função a cada elemento da coleção, o que é uma operação comum.
arr = [1, 2, 3]
new_arr = arr.map do |element|
element * 2
end
new_arr
# => [2, 4, 6]
Com um fluxo de controlo como o if, também podes filtrar quais os elementos a transformar.
arr = [1, 2, 3]
new_arr = arr.map do |element|
if element.odd?
element * 2
else
element
end
end
new_arr
# => [2, 2, 6]
Por vezes precisas de saber o índice do elemento sobre o qual estás a iterar; para isso, podes usar o método each_with_index ou map_with_index.
Ele passa cada elemento e o respetivo índice a um bloco, e o método também aceita um argumento opcional para especificar o índice inicial.
arr = [1, 2, 3]
arr.each_with_index(4) do |element, index|
puts "Element: #{element}, Index: #{index}"
end
# Output:
# Element: 1, Index: 4
# Element: 2, Index: 5
# Element: 3, Index: 6
Repara que o método each_char_with_index também está disponível para objetos String.
O método sum devolve a soma de todos os elementos da coleção; também aceita um bloco opcional para transformar os elementos antes de os somar.
Também aceita um argumento opcional para especificar o valor inicial da soma.
arr = [1, 2, 3]
arr.sum
# => 6
arr.sum(2) { |n| n * 2 }
# => 14
reduce, ou fold como é conhecido noutras linguagens, é um método que combina todos os elementos da coleção, usando um método de combinação e um valor inicial.
O método reduce tem um acumulador que é passado ao bloco; o acumulador é o resultado da iteração anterior.
Isto torna-se um processo recursivo que combina todos os elementos da coleção.
arr = [1, 2, 3]
arr.reduce(0) do |acc, n|
acc + n
end
# => 6
reduce pode parecer semelhante a sum, mas reduce é mais flexível, porque te permite especificar o valor inicial do acumulador e o método de combinação.
reduce pode ser usado para implementar sum, count e outros métodos.
No jogo de cartas Spellbound Steel, tens um baralho de cartas com diferentes personagens, como warriors, mages e rogues, ou diferentes feitiços, como fireball, ice storm e lightning bolt. O jogo tem entre dois e quatro jogadores, e o objetivo é derrotar os outros jogadores jogando cartas com níveis de poder mais altos.
Queres ganhar vantagem no jogo criando vários métodos para calcular, ordenar e comparar os níveis de poder das cartas.
Não tens a certeza se uma carta está no baralho ou não. Por isso, queres um método que te diga onde podes encontrar a primeira carta do baralho do tipo que procuras. Ou, se a carta não estiver no baralho, também queres saber isso.
Implementa um método SpellboundSteel.find_card? que recebe um Array de cartas do tipo String e uma carta do tipo String a encontrar.
O método deve devolver o índice da primeira carta do baralho que corresponde à carta a encontrar.
Se a carta não estiver no baralho, o método deve devolver nil.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt", "Mage"]
SpellboundSteel.find_card?(cards, "Mage")
# => 1
Para tornar as cartas mais fáceis de ler, queres escrever todos os nomes das cartas com maiúscula inicial. Assim consegues ver facilmente os nomes das cartas quando as jogas.
Implementa um método SpellboundSteel.capitalize_cards que recebe um Array de cartas do tipo String e devolve as cartas com a primeira letra de cada nome em maiúscula.
cards = ["warrior", "mage", "rogue", "fireball", "ice storm", "lightning bolt"]
SpellboundSteel.capitalize_cards(cards)
# => ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
No jogo, é importante calcular o nível de poder de todas as cartas. O poder de todas as cartas pode ver-se na seguinte tabela:
| Tipo de carta | Nível de poder |
|---|---|
| Warrior | 10 |
| Mage | 20 |
| Rogue | 30 |
| Fireball | 15 |
| Ice Storm | 25 |
| Lightning Bolt | 35 |
Implementa um método SpellboundSteel.calculate_power_level que recebe um Array de cartas do tipo String e devolve o nível de poder total de todas as cartas.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
SpellboundSteel.calculate_power_level(cards)
# => 135
No jogo, existem as chamadas cartas amaldiçoadas. Estas cartas são cartas ocultas que só podem ser descodificadas pelo jogador que as jogou. Para descodificares a carta, tens de retirar todos os segundos carateres do nome da carta. Isso dá-te o nome da carta.
Implementa um método SpellboundSteel.decode_card que recebe uma carta do tipo String e devolve a carta descodificada.
SpellboundSteel.decode_card("Wbalrrrlimoqr")
# => "Warrior"
Inscreve-te no Exercism para aprenderes e dominares Crystal com 26 conceitos142 exercícios, e mentoria humana real, tudo grátis.