In programmazione, l'iterazione si riferisce al processo di ripetere un blocco di codice più volte. Questo può essere fatto usando concetti come i cicli while e i cicli until. Può anche essere fatto usando la ricorsione, cioè una funzione che chiama se stessa.
Tuttavia, il più delle volte vuoi iterare su una collezione di elementi, come un Array, un Range o una String.
Sia la classe Array che la classe Range in Crystal includono il modulo Enumerable, che fornisce una serie di metodi per iterare sugli elementi.
La classe String, invece, ha il suo insieme di metodi per iterare sui caratteri.
Inoltre Crystal non ha alcuna istruzione for come altri linguaggi. Al suo posto ha diversi metodi che si possono usare per iterare.
Il modo più comune per iterare su una collezione è usare il metodo each, che passa a un blocco ogni elemento della collezione.
Questo si può fare facilmente con un Range.
Supponi di voler iterare da 1 a 3: puoi usare il metodo each per iterare sull'intervallo.
(1..3).each do |n|
puts n
end
# Output:
# 1
# 2
# 3
Ancora più semplice: se vuoi solo iterare un certo numero di volte, puoi usare il metodo times, che esiste sulla classe Int.
3.times do |n|
puts n
end
# Output:
# 0
# 1
# 2
String
Una String è una sequenza di caratteri e non appartiene al modulo Enumerable, il che significa che ha il suo insieme di metodi per iterare sui caratteri.
Il modo più comune per iterare su una String è usare il metodo each_char, che passa a un blocco ogni carattere della String.
Il metodo each_char passa al blocco un oggetto Char e non un oggetto String.
str = "hello"
str.each_char { |char| puts char }
# Output:
# h
# e
# l
# l
# o
Un altro modo per iterare su una String è usare il metodo each_line.
Questo metodo si usa soprattutto quando si legge un file riga per riga.
str = "hello\nworld"
str.each_line do |line|
puts line
end
# Output:
# hello
# world
Enumerable
Il modulo Enumerable fornisce una serie di metodi per iterare sugli elementi di una collezione.
Le collezioni che includono il modulo Enumerable sono Array, Range, Hash, Set e altre; queste ultime saranno trattate nei concetti successivi.
Il modo più comune per iterare su un Array è usare il metodo each, che passa a un blocco ogni elemento dell'Array.
arr = [5, 2, 3]
arr.each do |element|
puts element
end
# Output:
# 5
# 2
# 3
Il metodo map è un altro modo per iterare su un Array: restituisce un nuovo Array che contiene i risultati dell'applicazione del blocco a ogni elemento.
È simile al metodo each, ma restituisce un nuovo Array con gli elementi trasformati.
La trasformazione è molto utile quando vuoi applicare una funzione a ogni elemento della collezione, un'operazione comune.
arr = [1, 2, 3]
new_arr = arr.map do |element|
element * 2
end
new_arr
# => [2, 4, 6]
Con un flusso di controllo come if puoi anche filtrare quali elementi trasformare.
arr = [1, 2, 3]
new_arr = arr.map do |element|
if element.odd?
element * 2
else
element
end
end
new_arr
# => [2, 2, 6]
A volte ti serve conoscere l'indice dell'elemento su cui stai iterando: per farlo puoi usare il metodo each_with_index o map_with_index.
Passa a un blocco ogni elemento e il suo indice; il metodo accetta anche un argomento opzionale per specificare l'indice di partenza.
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
Nota che il metodo each_char_with_index è disponibile anche per gli oggetti String.
Il metodo sum restituisce la somma di tutti gli elementi della collezione; accetta anche un blocco opzionale per trasformare gli elementi prima di sommarli.
Accetta inoltre un argomento opzionale per specificare il valore iniziale della somma.
arr = [1, 2, 3]
arr.sum
# => 6
arr.sum(2) { |n| n * 2 }
# => 14
reduce, o fold come è noto in altri linguaggi, è un metodo che, con un metodo di combinazione e un valore iniziale, combina tutti gli elementi della collezione.
Il metodo reduce ha un accumulatore che viene passato al blocco; l'accumulatore è il risultato dell'iterazione precedente.
Si tratta di un processo ricorsivo che combina tutti gli elementi della collezione.
arr = [1, 2, 3]
arr.reduce(0) do |acc, n|
acc + n
end
# => 6
reduce può sembrare simile a sum, ma è più flessibile perché ti permette di specificare il valore iniziale dell'accumulatore e il metodo di combinazione.
reduce può essere usato per implementare sum, count e altri metodi.
Nel gioco di carte Spellbound Steel, hai un mazzo di carte con personaggi diversi, come warriors, mages e rogues, oppure incantesimi diversi, come fireball, ice storm e lightning bolt. In partita ci sono da due a quattro giocatori, e l'obiettivo è sconfiggere gli altri giocatori giocando carte con livelli di potenza più alti.
Vuoi avere la meglio nel gioco creando vari metodi per calcolare, ordinare e confrontare i livelli di potenza delle carte.
Non sei sicuro che una carta sia nel mazzo oppure no. Perciò vuoi un metodo che ti dica dove trovare la prima carta del mazzo del tipo che stai cercando. Oppure, se la carta non è nel mazzo, vuoi saperlo lo stesso.
Implementa un metodo SpellboundSteel.find_card? che prende un Array di carte String e una carta String da trovare.
Il metodo deve restituire l'indice della prima carta del mazzo che corrisponde alla carta da cercare.
Se la carta non è nel mazzo, il metodo deve restituire nil.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt", "Mage"]
SpellboundSteel.find_card?(cards, "Mage")
# => 1
Per rendere le carte più facili da leggere, vuoi mettere in maiuscolo tutti i caratteri nei nomi delle carte. Così puoi vedere facilmente i nomi delle carte quando le giochi.
Implementa un metodo SpellboundSteel.capitalize_cards che prende un Array di carte String e restituisce le carte con tutti i caratteri in maiuscolo.
cards = ["warrior", "mage", "rogue", "fireball", "ice storm", "lightning bolt"]
SpellboundSteel.capitalize_cards(cards)
# => ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
Nel gioco è importante calcolare il livello di potenza di tutte le carte. La potenza di tutte le carte è visibile nella tabella seguente:
| Tipo di carta | Livello di potenza |
|---|---|
| Warrior | 10 |
| Mage | 20 |
| Rogue | 30 |
| Fireball | 15 |
| Ice Storm | 25 |
| Lightning Bolt | 35 |
Implementa un metodo SpellboundSteel.calculate_power_level che prende un Array di carte String e restituisce il livello di potenza totale di tutte le carte.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
SpellboundSteel.calculate_power_level(cards)
# => 135
Nel gioco esistono quelle che vengono chiamate carte maledette. Queste carte sono una carta nascosta che può essere decodificata solo dal giocatore che l'ha giocata. Per decodificare la carta devi prendere un carattere sì e uno no nel nome della carta e rimuoverlo. Otterrai così il nome della carta.
Implementa un metodo SpellboundSteel.decode_card che prende una carta String e restituisce la carta decodificata.
SpellboundSteel.decode_card("Wbalrrrlimoqr")
# => "Warrior"
Iscriviti a Exercism per imparare e padroneggiare Crystal con 26 concetti142 esercizi e il mentoring di persone reali, tutto gratis.