Ruby 数组方法详解:掌握高效数据处理技巧
简介
数组可以在程序中表示数据列表。一旦您有了数组中的数据,就可以对其进行排序、去除重复项、反转顺序、提取数组的部分或者在数组中搜索特定数据。您还可以将数组转换成字符串,将一个数据数组转换成另一个数组,并将一个数组合并成一个单一的值。
在本教程中,您将探索一些 Ruby 提供的最实用的方法,用于处理存储在数组中的数据。
在您学习本教程时,您会看到一些方法以感叹号(!
)结尾。这些方法通常会具有副作用,例如改变原始值或引发异常。在本教程中,许多方法都有一个相关的带有这个后缀的方法。
您也会遇到以问号(?
)结尾的方法。这些方法会返回一个布尔值。
这是 Ruby 中常用的命名约定。它并不是在程序级别上强制执行的,而只是识别方法可预期行为的另一种方式。
让我们从几种访问元素的方法开始探索数组方法。
访问元素
如果您已经按照 Ruby 中的数组教程操作过了,您就会知道可以通过索引访问单个元素,索引是从零开始的,就像这样:
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[0] # "Tiger"
sharks[1] # "Great White"
sharks[-1] # "Angel"
您可能还记得,您可以使用 first
和 last
方法来获取数组的第一个和最后一个元素。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.first # "Tiger"
sharks.last # "Angel"
最后,当您访问一个不存在的元素时,您将得到 nil
。但是如果您想要获得错误而不是 nil
,请使用 fetch
方法。
sharks.fetch(42)
IndexError: index 42 outside of array bounds: -4...4
如果您希望指定自己的默认设置而不是引发错误,也可以这样做。
sharks.fetch(42, "Nope") # "Nope"
现在让我们来看看如何从数组中获取多个元素。
获取多个元素
这是文章《如何在 Ruby 中使用数组方法》的第2部分(共12部分)。
内容片段: 有时您可能希望从数组中获取一部分值,而不仅仅是单个元素。
如果您指定一个起始索引,然后指定要获取的元素数量,您将获得一个包含这些值的新数组。例如,您可以像这样从 sharks
数组中获取两个中间条目:
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[1,2] # ["Great White", "Hammerhead"]
我们从索引 1 开始,即“Great White”,并指定我们想要两个元素,然后我们得到一个包含“Great White”和“Hammerhead”的新数组。
您可以使用 slice
方法来做相同的事情:
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.slice(1,2) # ["Great White", "Hammerhead"]
slice
方法也会返回一个新的数组,不改变原始数组。然而,如果您使用 slice!
方法,原始数组也会被改变。
take
方法允许您从数组的开头获取指定数量的条目:
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.take(2) # ["Tiger", "Great White"]
有时您想从数组中随机抓取一个值,而不是特定的一个。让我们探索一下如何实现。
从数组中获取一个随机条目
也许您正在开发一款赌博游戏,或者您正在编写一个能够选择竞赛获胜者的程序。这些事情都需要某种随机值。一个常见的解决方案就是将可能的选择放入一个数组中,然后随机选择一个索引。
要从数组中获取一个随机元素,您可以生成一个介于 0 到数组最后一个索引之间的随机索引,并将其用作索引来检索值,但还有一种更简单的方法:sample
方法从数组中随机选择一个元素。
让我们用它从一组预设答案中随机获取一个答案,创造一个原始版本的“神奇八球”游戏。
以下是 8ball.rb
的代码:
answers = ["Yes", "No", "Maybe", "Ask again later"]
print answers.sample
Maybe
sample
方法还接受一个参数,该参数会返回一个随机条目的数组。所以,如果您需要多个随机条目,只需提供您想要的数量。
这是文章《如何在 Ruby 中使用数组方法》的第3部分(共12部分)。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sample = sharks.sample(2)
print sample
["Whale", "Great White"]
接下来,让我们看看如何在数组中找到特定的元素。
寻找和过滤元素
这是文章《如何在 Ruby 中使用数组方法》的第4部分(共12部分)。
当你在数组中寻找特定元素时,通常需要遍历数组的元素直到找到目标。然而,Ruby 数组提供了几种专门设计来简化数组搜索过程的方法。
如果你只是想查看一个元素是否存在,可以使用 include?
方法。如果指定的数据是数组的一个元素,该方法会返回 true
。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sharks.include? "Tiger" # => true
["a", "b", "c"].include? 2 # => false
然而,include?
方法需要完全匹配,因此你不能用于查找部分单词或进行大小写不敏感的匹配。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sharks.include? "Tiger" # => true
sharks.include? "tiger" # => false (大小写不匹配)
sharks.include? "ti" # => false (部分匹配不生效)
find
方法(或 detect
方法,它们是别名)用于定位并返回数组中与你指定条件匹配的第一个元素。
例如,要找到包含字母 ‘a’ 的 sharks
数组中的第一个条目,你可以使用 each
方法逐个比较每个条目,并在找到第一个匹配项时停止迭代,如下所示:
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = nil
sharks.each do |shark|
if shark.include?("a")
result = shark
break
end
end
# result 现在是 "Hammerhead"
或者,你可以使用更简洁的 find
方法来完成相同的任务:
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.find { |item| item.include?("a") }
puts result
Hammerhead
这是文章《如何在 Ruby 中使用数组方法》的第5部分(共12部分)。
对于数组中的每个元素,find
方法会执行您提供的代码块。如果代码块中的最后一个表达式评估为真,find
方法将返回该值并停止迭代。如果在遍历所有元素后没有找到任何值,它会返回 nil
。
select
方法的工作方式类似,但不同之处在于它会构造一个新数组,其中包含所有满足条件的元素,而不仅仅返回一个单一值并停止处理。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
results = sharks.select {|item| item.include?("a")}
print results
["Hammerhead", "Great White", "Whale"]
reject
方法返回一个不符合条件的新数组。你可以把它看作是一个过滤器,用于删除不想要的元素。以下是一个例子,拒绝所有包含字母 ‘a’ 的条目。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
results = sharks.reject {|item| item.include?("a")}
print results
["Tiger"]
select
和 reject
都会返回一个新的数组,原始数组保持不变。不过,如果使用 select!
和 reject!
方法,原始数组将被修改。
find_all
方法是 select
的别名,但是没有 find_all!
方法。
接下来,让我们来看一下如何对数组的值进行排序。
对数组进行排序
排序数据是一种常见的做法。您可能需要按字母顺序排列名单或按从小到大的顺序排序数字。
Ruby 的数组有一个 reverse
方法,可以将数组中的元素顺序反转。如果你有一个已经排好序的数据列表,使用 reverse
可以快速地将元素翻转过来。
sharks = ["Angel", "Great White", "Hammerhead", "Tiger"]
reversed_sharks = sharks.reverse
print reversed_sharks
["Tiger", "Hammerhead", "Great White", "Angel"]
这是文章《如何在 Ruby 中使用数组方法》的第6部分(共12部分)。
["Tiger", "Hammerhead", "Great White", "Angel"]
reverse
方法返回一个新的数组,并且不修改原始数组。如果你想改变原始数组,可以使用 reverse!
方法。
然而,反转数组并不总是最有效或最实际的数据排序方法。你可以使用 sort
方法按照所需的方式对数组元素进行排序。
对于简单的字符串数组或数字数组,sort
方法是高效的,并且会给你想要的结果。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort
print sorted_sharks
["Angel", "Great White", "Hammerhead", "Tiger"]
然而,如果你想以不同的方式对事物进行排序,你需要告诉 sort
方法如何操作。sort
方法接受一个 Ruby 块,该块使你能够访问数组中的元素,以便进行比较。
为了进行比较,你使用比较运算符(<=>
),通常称为“太空船运算符”。这个运算符比较两个 Ruby 对象,并返回 -1
(如果左边的对象较小)、0
(如果两个对象相等)或 1
(如果左边的对象较大)。
1 <=> 2 # -1
2 <=> 2 # 0
2 <=> 1 # 1
Ruby 的 sort
方法接受一个必须返回 -1
、0
或 1
的块,然后用它来对数组中的值进行排序。
以下是一个明确比较数组元素并按升序排序的示例:
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort{|a,b| a <=> b }
print sorted_sharks
a
和 b
变量代表数组中进行比较的单个元素。结果如下所示:
["Angel", "Great White", "Hammerhead", "Tiger"]
要将鲨鱼按相反的顺序排序,只需反转比较对象即可。
sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort{|a,b| b <=> a }
print sorted_sharks
这是文章《如何在 Ruby 中使用数组方法》的第7部分(共12部分)。
["Tiger", "Hammerhead", "Great White", "Angel"]
当数组包含简单数据类型(如整数、浮点数和字符串)时,排序方法非常好用。但是,当数组包含更复杂的对象时,你需要做更多的工作。
这是一个散列数组,每个散列代表一条鲨鱼。
sharks = [
{:name: "Hammerhead"},
{:name: "Great white"},
{:name: "Angel"}
]
用 sort
对这个进行排序并不容易。对数组调用 sort
方法失败了。
sharks.sort
ArgumentError: comparison of Hash with Hash failed
为了进行比较,我们需要告诉排序函数我们想要比较的内容。因此,我们将比较哈希表中“:name”键的值。
sorted_sharks.sort{|a, b| a[:name] <=> b[:name]}
print sorted_sharks
[{:name=>"Angel"}, {:name=>"Great white"}, {:name=>"Hammerhead"}]
当你处理更复杂的结构时,你可能想考虑使用 sort_by
方法,它使用了更高效的排序算法。sort_by
方法接受一个只需要一个参数的块,该参数是数组中当前元素的引用。
sharks = [
{:name: "Hammerhead"},
{:name: "Great white"},
{:name: "Angel"}
]
sorted_sharks = sharks.sort_by{|shark| shark[:name] }
print sorted_sharks
[{:name=>"Angel"}, {:name=>"Great white"}, {:name=>"Hammerhead"}]
sort_by
方法实现了一种 Schwartzian 转换,这是一种最适合根据特定键的值比较对象的排序算法。因此,当比较对象集合时,您会发现自己更频繁地使用 sort_by
,因为它更高效。
无论是 sort
还是 sort_by
都返回一个新的数组,原始数组保持不变。如果你想修改原始数组,请使用 sort!
和 sort_by!
。
除了对数值进行排序外,您可能还想要去除重复项。
删除重复元素
这是文章《如何在 Ruby 中使用数组方法》的第8部分(共12部分)。
内容片段: 有时你会遇到包含重复数据的数据列表。虽然你可以通过遍历数组来过滤掉重复项,但 Ruby 的 uniq
方法能让这个过程变得异常简单。uniq
方法会返回一个新数组,其中所有重复的值都已被移除。
[1,2,3,4,1,5,3].uniq # [1,2,3,4,5]
有时,在合并两组数据时可能会出现重复项。以这两组鲨鱼数组为例:
sharks = ["Tiger", "Great White"]
new_sharks = ["Tiger", "Hammerhead"]
如果我们将它们直接相加,就会得到重复的条目。
sharks + new_sharks
# ["Tiger", "Great White", "Tiger", "Hammerhead"]
你可以使用 uniq
方法来删除重复项,但更好的做法是完全避免引入它们。不要将数组相加,而是使用管道操作符 |
将数组合并在一起。
sharks | new_sharks
# ["Tiger", "Great White", "Hammerhead"]
Ruby 数组还支持减法操作,这意味着你可以从 sharks
数组中减去 new_sharks
数组,从而只得到新的或不重复的值。
sharks = ["Tiger", "Great White"]
new_sharks = ["Tiger", "Hammerhead"]
sharks - new_sharks # ["Great White"]
数据转换
接下来,让我们来看看如何操作每个元素的值。
这是文章《如何在 Ruby 中使用数组方法》的第9部分(共12部分)。
map
方法及其别名 collect
可以对数组内容进行转换,即对数组中的每个元素执行一个操作。
例如,您可以使用 map
函数对数组的每个元素进行算术运算,并创建一个包含新值的新数组。
numbers = [2,4,6,8]
# 计算每个数字的平方
squared_numbers = numbers.map {|number| number * number}
print squared_numbers
squared_numbers
变量是原始数字的平方数组。
[4, 16, 36, 64]
在 Web 应用程序中,map
经常用于将数组转换为 HTML 下拉列表中的元素。这是一个非常简化的示例,展示了具体的实现方式。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
options = sharks.map {|shark| "<option>#{shark}</option>"}
print options
现在,options
数组中的每个鲨鱼名称都被包裹在 HTML 标签中。
["<option>Hammerhead</option>", "<option>Great White</option>", "<option>Tiger</option>", "<option>Whale</option>"]
使用 map
会返回一个新数组,而不会修改原始数组。使用 map!
则会修改现有数组。同时,请记住 map
有一个名为 collect
的别名。在您的代码中应该保持一致,只使用其中一个。
由于 map
函数返回一个新数组,这个数组可以进一步进行转换和操作,甚至可以转换为字符串。接下来让我们来看一下这个。
将数组转换为字符串
在 Ruby 中,所有对象都有一个 to_s
方法,用于将对象转换为字符串。这就是 print
语句所使用的方法。考虑到我们的鲨鱼数组:
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
调用 to_s
方法会创建以下字符串:
"[\"Hammerhead\", \"Great White\", \"Tiger\", \"Whale\"]"
这对于调试来说很好,但在一个真正的程序中并不是非常有用。
join
方法可以将一个数组转换为一个字符串,同时还可以让您更好地控制如何组合元素。join
方法需要一个参数来指定您希望用作分隔符的字符。要将一个鲨鱼数组转换为由空格分隔的鲨鱼名称字符串,您可以像这样操作:
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(" ")
print result
Hammerhead Great White Tiger Whale
如果您想让每个鲨鱼名称用逗号和空格分隔,那么请使用逗号和空格作为分隔符。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(", ")
print result
Hammerhead, Great White, Tiger, Whale
如果您在join
方法中不指定参数,仍然会得到一个字符串,但它不会有任何分隔符。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join
print result
HammerheadGreat WhiteTigerWhale
将join
方法与map
方法结合使用,是快速将数据数组转换为输出字符串的有效方式。首先使用map
转换数据中的每个元素,然后使用join
将整个数组转换为可打印的字符串。还记得我们将sharks
数组转换为HTML元素数组的例子吗?这次我们将使用join
将元素数组转换为一个以换行符作为分隔符的字符串。
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
options = sharks.map { |shark| "<option>#{shark}</option>" }
output = options.join("\n")
print output
<option>Hammerhead</option>
<option>Great White</option>
<option>Tiger</option>
<option>Whale</option>
除了将数组转换为字符串,您可能还希望获取其总和或进行其他类型的转换,以获得一个单一的数值。接下来我们将探讨这一点。
将数组缩减为单一值
这是文章《如何在 Ruby 中使用数组方法》的第11部分(共12部分)。
当您处理一组数据时,经常需要将数据“折叠”成一个单一的值,例如计算总和。传统上,您可以通过使用变量和each
方法来实现这一目标。
result = 0
[1, 2, 3].each {|num| result += num}
print result
6
然而,Ruby 提供了更简洁、功能更强大的reduce
方法(也称为inject
),它可以替代上述做法。reduce
方法会遍历数组,并通过对每个元素执行一个二元操作来维护一个累加值。
reduce
方法可以接受一个初始值作为累加的起点,并且其代码块(block)包含两个局部变量:一个是对累加结果的引用,另一个是对当前元素的引用。在代码块内部,您可以定义计算最终结果的逻辑。
例如,为了对数组进行求和,我们可以将初始值设置为0
,然后在代码块中将当前元素的值加到累加结果上。
output = [1,2,3].reduce(0) {|result, current| result += current }
print output
6
如果您打算将累加结果初始化为0
,可以省略reduce
方法的初始参数,只传递代码块。在这种情况下,reduce
方法会自动将数组的第一个元素作为初始值。
output = [1,2,3].reduce {|result, current| result += current }
print output
6
reduce
方法还允许您通过传递一个符号来指定一个二元方法(即接受另一个对象作为参数的方法),并在数组的每个元素上执行该方法。然后,reduce
方法会利用这些操作的结果来生成一个单一的最终值。
在 Ruby 中,当您写2 + 2
时,实际上是在整数2
上调用了+
方法。
2.+(2) # 4
Ruby 使用了一些语法糖,使得您可以更简洁地将其表示为2 + 2
。
reduce
方法允许通过传递方法名称的符号形式来指定二元方法。这意味着您可以将:+
传递给reduce
方法,从而简洁地实现数组求和。
output = [1, 2, 3].reduce(:+)
print output
6
尽管reduce
方法常用于对列表中的数字进行求和,但它也可以用于转换数值。请记住,reduce
的目的是将数组“缩减”为单个值,但这个“单个值”并没有规定不能是另一个数组。
假设我们有一个包含各种类型值的列表,我们需要将其中的有效数字转换为整数,并过滤掉无法转换的值。
我们可以使用reject
方法排除非数字值,然后使用map
方法将剩余的值转换为整数。但更高效的方式是,我们可以一步到位地使用reduce
方法来完成所有操作。下面是具体的方法:
首先,使用一个空数组作为reduce
方法的初始值。然后,在代码块中,尝试使用Integer()
方法将当前值转换为整数。如果值无法转换为整数,Integer()
方法会抛出异常,您可以捕获该异常并将值设置为nil
。
最后,将这个转换后的值添加到累加数组中,但仅当它不是nil
时才添加。
以下是实现此功能的代码示例。请尝试运行它:
转换_value_数组.rb
values = ["1", "2", "a", "3"]
integers = values.reduce([]) do |array, current|
val = Integer(current) rescue nil
array.push(val) unless val.nil?
array
end
print integers
[1, 2, 3]
当你需要将一组元素列表转换为单个值时,通常可以使用 reduce
方法来解决。
结论
在本教程中,你学习了多种处理数组的方法。你掌握了如何获取单个元素、通过搜索数组检索值、对元素进行排序,以及转换数据以创建新的数组、字符串和总和。你可以运用这些概念来解决许多常见的 Ruby 编程问题。
请务必查看以下相关教程,以继续探索如何在 Ruby 中处理数据: