轨道
/
Crystal
Crystal
/
练习
/
着魔之钢
着魔之钢

着魔之钢

学习练习

简介

在编程中,迭代指的是重复执行一段代码块的过程。这可以通过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 中,你有一副卡牌,牌面要么是不同的角色,比如战士、法师和盗贼,要么是不同的法术,比如火球、冰风暴和闪电箭。 游戏有两到四名玩家,目标是通过打出能量等级更高的卡牌来击败其他玩家。

你想通过编写各种方法来计算、排序和比较卡牌的能量等级,从而在游戏中占据上风。

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. 计算所有卡牌的能量等级

在游戏中,计算所有卡牌的能量等级很重要。所有卡牌的能量可以在下表中看到:

Card Type Power Level
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,全部免费。