Na programação, iteração é o processo de repetir um bloco de código várias vezes.
Isso pode ser feito com conceitos como laços while e laços until.
Também pode ser feito com recursão, que é uma função que chama a si mesma.
Mas, na maioria das vezes, você quer iterar sobre uma coleção de itens, como um Array, um Range ou uma String.
Tanto a classe Array quanto a classe Range do Crystal incluem o módulo Enumerable, que oferece vários métodos para iterar sobre os elementos.
Já a classe String tem seu próprio conjunto de métodos para iterar sobre os caracteres.
O Crystal também não tem nenhuma instrução for como outras linguagens.
Em vez disso, ele tem vários métodos que podem ser usados para iterar.
A forma mais comum de iterar sobre uma coleção é usar o método each, que passa cada elemento da coleção para um bloco.
Isso é fácil de fazer com um Range.
Digamos que você queira fazer um laço de 1 a 3; para isso, pode usar o método each para iterar sobre o intervalo.
(1..3).each do |n|
puts n
end
# Output:
# 1
# 2
# 3
Mais simples ainda: se você só quer iterar um certo número de vezes, pode 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 caracteres e não pertence ao módulo Enumerable, o que significa que ela tem seu próprio conjunto de métodos para iterar sobre os caracteres.
A forma mais comum de iterar sobre uma String é usar o método each_char, que passa cada caractere da String para um bloco.
O método each_char passa um objeto Char para o 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.
Esse método é usado principalmente ao ler um arquivo linha por linha.
str = "hello\nworld"
str.each_line do |line|
puts line
end
# Output:
# hello
# world
Enumerable
O módulo Enumerable oferece vários métodos para iterar sobre os elementos de uma coleção.
Entre as coleções que incluem o módulo Enumerable estão Array, Range, Hash, Set e 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 para 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: ele retorna um novo Array com os resultados da aplicação do bloco a cada elemento.
Ele é parecido com o método each, mas retorna um novo Array com os elementos transformados.
A transformação é muito útil quando você quer 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 controle como o if, você também pode filtrar quais elementos 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]
Às vezes você precisa saber o índice do elemento sobre o qual está iterando; para isso, pode usar o método each_with_index ou map_with_index.
Ele passa cada elemento e seu índice para 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
Repare que o método each_char_with_index também está disponível para objetos String.
O método sum retorna a soma de todos os elementos da coleção e também aceita um bloco opcional para transformar os elementos antes de somá-los.
Ele 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 em outras linguagens, é um método que, com um método de combinação e um valor inicial, combina todos os elementos da coleção.
O método reduce tem um acumulador que é passado para o bloco; o acumulador é o resultado da iteração anterior.
Isso vira 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 parecido com sum, mas reduce é mais flexível, porque 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, você tem um baralho com personagens diferentes, como warriors, mages e rogues, ou feitiços diferentes, como fireball, ice storm e lightning bolt. A partida tem de dois a quatro jogadores, e o objetivo é derrotar os outros jogadores jogando cartas com níveis de poder mais altos.
Você quer levar a melhor no jogo criando vários métodos para calcular, ordenar e comparar os níveis de poder das cartas.
Você não tem certeza se uma carta está no baralho ou não. Por isso, você quer um método que diga onde encontrar a primeira carta do tipo que você procura. E, se a carta não estiver no baralho, você também quer saber disso.
Implemente um método SpellboundSteel.find_card? que recebe um Array de cartas String e uma carta String a ser encontrada.
O método deve retornar o índice da primeira carta do baralho que corresponde à carta procurada.
Se a carta não estiver no baralho, o método deve retornar nil.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt", "Mage"]
SpellboundSteel.find_card?(cards, "Mage")
# => 1
Para deixar as cartas mais fáceis de ler, você quer colocar todos os caracteres dos nomes das cartas em maiúscula. Assim, você enxerga os nomes das cartas com facilidade na hora de jogá-las.
Implemente um método SpellboundSteel.capitalize_cards que recebe um Array de cartas String e retorna as cartas com todos os caracteres 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 ser visto na tabela a seguir:
| Tipo de carta | Nível de poder |
|---|---|
| Warrior | 10 |
| Mage | 20 |
| Rogue | 30 |
| Fireball | 15 |
| Ice Storm | 25 |
| Lightning Bolt | 35 |
Implemente um método SpellboundSteel.calculate_power_level que recebe um Array de cartas String e retorna 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. Essas cartas são cartas ocultas que só podem ser decodificadas pelo jogador que as jogou. Para decodificar a carta, você precisa pegar cada segundo caractere do nome da carta e removê-lo. Isso revela o nome da carta.
Implemente um método SpellboundSteel.decode_card que recebe uma carta String e retorna a carta decodificada.
SpellboundSteel.decode_card("Wbalrrrlimoqr")
# => "Warrior"
Crie sua conta no Exercism para aprender e dominar Crystal com 26 conceitos142 exercícios e mentoria humana de verdade, tudo de graça.