Ruby 数组方法详解:掌握高效数据处理技巧

简介

数组可以在程序中表示数据列表。一旦您有了数组中的数据,就可以对其进行排序、去除重复项、反转顺序、提取数组的部分或者在数组中搜索特定数据。您还可以将数组转换成字符串,将一个数据数组转换成另一个数组,并将一个数组合并成一个单一的值。

在本教程中,您将探索一些 Ruby 提供的最实用的方法,用于处理存储在数组中的数据。

在您学习本教程时,您会看到一些方法以感叹号(!)结尾。这些方法通常会具有副作用,例如改变原始值或引发异常。在本教程中,许多方法都有一个相关的带有这个后缀的方法。

您也会遇到以问号(?)结尾的方法。这些方法会返回一个布尔值。

这是 Ruby 中常用的命名约定。它并不是在程序级别上强制执行的,而只是识别方法可预期行为的另一种方式。

让我们从几种访问元素的方法开始探索数组方法。

访问元素

如果您已经按照 Ruby 中的数组教程操作过了,您就会知道可以通过索引访问单个元素,索引是从零开始的,就像这样:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[0] # "Tiger"
sharks[1] # "Great White"
sharks[-1] # "Angel"

您可能还记得,您可以使用 firstlast 方法来获取数组的第一个和最后一个元素。

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 方法还接受一个参数,该参数会返回一个随机条目的数组。所以,如果您需要多个随机条目,只需提供您想要的数量。

随机鲨鱼.rb

这是文章《如何在 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"]

selectreject 都会返回一个新的数组,原始数组保持不变。不过,如果使用 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 方法接受一个必须返回 -101 的块,然后用它来对数组中的值进行排序。

以下是一个明确比较数组元素并按升序排序的示例:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort{|a,b| a <=> b }
print sorted_sharks

ab 变量代表数组中进行比较的单个元素。结果如下所示:

输出
["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 方法需要一个参数来指定您希望用作分隔符的字符。要将一个鲨鱼数组转换为由空格分隔的鲨鱼名称字符串,您可以像这样操作:

鲨鱼加入.rb

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(" ")
print result
输出
Hammerhead Great White Tiger Whale

如果您想让每个鲨鱼名称用逗号和空格分隔,那么请使用逗号和空格作为分隔符。

鲨鱼加入.rb
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(", ")
print result
输出
Hammerhead, Great White, Tiger, Whale

如果您在join方法中不指定参数,仍然会得到一个字符串,但它不会有任何分隔符。

鲨鱼加入.rb
sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join
print result
输出
HammerheadGreat WhiteTigerWhale

join方法与map方法结合使用,是快速将数据数组转换为输出字符串的有效方式。首先使用map转换数据中的每个元素,然后使用join将整个数组转换为可打印的字符串。还记得我们将sharks数组转换为HTML元素数组的例子吗?这次我们将使用join将元素数组转换为一个以换行符作为分隔符的字符串。

map.rb —> map.rb
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 中处理数据:

bannerAds