魔咒之鋼

魔咒之鋼

學習練習

簡介

在程式設計中,疊代指的是重複執行程式區塊多次的過程。你可以使用 while 迴圈和 until 迴圈等概念來達成,也可以使用遞迴,也就是一個會呼叫自己的函式。

不過,大多數時候你想疊代的是一組項目,例如Array、Range或String。Crystal 中的Array和Range類別都包含Enumerable模組,它提供了許多疊代元素的方法。而String類別則有自己的一套方法,用來疊代字元。

Crystal 也沒有其他語言那樣的 for 敘述。它改用幾個方法來進行疊代。

疊代

對集合進行疊代最常見的方式,是使用each方法,它會把集合中的每個元素傳給區塊。用Range就能輕鬆做到這點。假設你想在 1 到 3 之間迴圈,就可以用each方法疊代這個範圍。

(1..3).each do |n|
  puts n
end

# Output:
# 1
# 2
# 3

更簡單的是,如果你只想疊代固定的次數,可以使用times方法,它定義在Int類別上。

3.times do |n|
  puts n
end

# Output:
# 0
# 1
# 2

疊代String

String是一串字元,不屬於Enumerable模組,也就是說它有自己的方法來疊代字元。疊代String最常見的方式是使用each_char方法,它會把String中的每個字元傳給區塊。

Note

each_char方法傳給區塊的是Char物件,而不是String物件。

str = "hello"
str.each_char { |char| puts char }

# Output:
# h
# e
# l
# l
# o

疊代String的另一種方式是使用each_line方法。這個方法大多用在逐行讀取檔案的時候。

str = "hello\nworld"
str.each_line do |line|
  puts line
end

# Output:
# hello
# world

疊代包含Enumerable模組的物件

Enumerable模組提供了許多方法,用來疊代集合中的元素。包含Enumerable模組的集合有Array、Range、Hash、Set等等,後面這些會在之後的概念中介紹。

疊代Array最常見的方式是使用each方法,它會把Array中的每個元素傳給區塊。

arr = [5, 2, 3]
arr.each do |element|
  puts element
end

# Output:
# 5
# 2
# 3

map

map方法是疊代Array的另一種方式,它會回傳一個新的Array,裡面是把區塊套用到每個元素後的結果。它和each方法很類似,但會回傳一個包含轉換後元素的新Array。當你想把某個函式套用到集合中的每個元素時,轉換就非常有用,而這是很常見的操作。

arr = [1, 2, 3]
new_arr = arr.map do |element|
  element * 2
end

new_arr
# => [2, 4, 6]

搭配if之類的控制流程,你還可以篩選要轉換哪些元素。

arr = [1, 2, 3]
new_arr = arr.map do |element|
  if element.odd?
    element * 2
  else
    element
  end
end

new_arr
# => [2, 2, 6]

搭配索引

有時候你需要知道目前疊代到的元素索引,這時可以使用each_with_index或map_with_index方法。它會把每個元素和它的索引傳給區塊,這個方法也接受一個選用引數來指定起始索引。

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

請注意,String物件也可以使用each_char_with_index方法。

sum

sum方法會回傳集合中所有元素的總和,它也接受選用的區塊,在加總前先轉換元素。它還接受一個選用引數,用來指定總和的初始值。

arr = [1, 2, 3]
arr.sum
# => 6

arr.sum(2) { |n| n * 2 }
# => 14

reduce

reduce,在其他語言中稱為 fold,是一個搭配合併方法和初始值,把集合中所有元素合併起來的方法。reduce方法有一個累加器會傳給區塊,累加器是前一次疊代的結果。這會形成一個遞迴的過程,把集合中的所有元素合併起來。

arr = [1, 2, 3]
arr.reduce(0) do |acc, n|
  acc + n
end
# => 6

reduce看起來可能和sum很像,但reduce更有彈性,因為它可以讓你指定累加器的初始值和合併方法。reduce可以用來實作sum、count等方法。

說明

在卡牌遊戲 Spellbound Steel 中,你的牌組裡有各種卡牌,上頭可能是不同的角色,例如 warrior、mage 和 rogue,也可能是不同的法術,例如 fireball、ice storm 和 lightning bolt。 遊戲中有二到四位玩家,目標是打出威力值更高的卡牌來擊敗其他玩家。

你想建立各種方法來計算、排序和比較卡牌的威力值,好在遊戲中取得優勢。

1. 在牌組中尋找卡牌

你不確定某張卡牌是否在牌組裡。 因此,你想要一個方法,能告訴你牌組中第一張你想找的卡牌在哪裡。 如果牌組裡沒有這張卡牌,你也想知道。

實作一個方法 SpellboundSteel.find_card?,它接受一個由 String 卡牌組成的 Array,以及一張要尋找的 String 卡牌。 這個方法應該回傳牌組中第一張符合要尋找卡牌的索引。 如果牌組裡沒有這張卡牌,方法應該回傳 nil。

cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt", "Mage"]
SpellboundSteel.find_card?(cards, "Mage")
# => 1

2. 將所有字元改成大寫

為了讓卡牌更容易閱讀,你想把卡牌名稱中的所有字元改成大寫。 這樣一來,你打出卡牌時就能輕易看清卡牌名稱。

實作一個方法 SpellboundSteel.capitalize_cards,它接受一個由 String 卡牌組成的 Array,並回傳所有字元都已改成大寫的卡牌。

cards = ["warrior", "mage", "rogue", "fireball", "ice storm", "lightning bolt"]
SpellboundSteel.capitalize_cards(cards)
# => ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]

3. 計算所有卡牌的威力值

在遊戲中,計算所有卡牌的威力值很重要。 所有卡牌的威力值可以在下表中看到:

卡牌類型 威力值
Warrior 10
Mage 20
Rogue 30
Fireball 15
Ice Storm 25
Lightning Bolt 35

實作一個方法 SpellboundSteel.calculate_power_level,它接受一個由 String 卡牌組成的 Array,並回傳所有卡牌的威力值總和。

cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
SpellboundSteel.calculate_power_level(cards)
# => 135

4. 解碼卡牌

遊戲中有所謂的詛咒卡牌。 這些卡牌是隱藏的卡牌,只有打出它的玩家才能解碼。 要解碼卡牌,你需要取出卡牌名稱中每隔一個的字元,並移除其餘的字元。 這樣就會得到卡牌的名稱。

實作一個方法 SpellboundSteel.decode_card,它接受一張 String 卡牌,並回傳解碼後的卡牌。

SpellboundSteel.decode_card("Wbalrrrlimoqr")
# => "Warrior"
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Crystal Exercism

準備好開始 魔咒之鋼 了嗎?

註冊 Exercism,透過 26 個概念142 個練習 和真人引導來學習並精通 Crystal,全部免費。