了解 Enumerable 模块中的 grep

Understanding grep within Enumerable module

非常感谢对我的第一个堆栈溢出的任何帮助 post!

我通常很困惑,为什么我每次尝试 运行 我的代码时都会收到空数组。 我正在创建一个方法来过滤性别“M”和 return 它的元素。

我确信有多种方法可以成功 运行 此代码,但我对使用 grep 及其功能很感兴趣。这是一条捷径,我认为学习它会很好。再次感谢您。

students = [
    {name: 'John', grade: 8, gender: 'M'},
    {name: 'Sarah', grade: 12, gender: 'F'},
    {name: 'Bob', grade: 16, gender: 'M'},
    {name: 'Johnny', grade: 2, gender: 'M'},
    {name: 'Ethan', grade: 4, gender: 'M'},
    {name: 'Paula', grade: 8, gender: 'F'},
    {name: 'Donald', grade: 5, gender: 'M'},
    {name: 'Jennifer', grade: 13, gender: 'F'},
    {name: 'Courtney', grade: 15, gender: 'F'},
    {name: 'Jane', grade: 9, gender: 'F'}
]
def is_male(gender)
  gender.grep(/M/) { |gend| gend[:gender] }
end
p is_male(students)

来自docs of Enumerable#grep

grep(pattern) → array
grep(pattern) { |obj| block } → array

Returns an array of every element in enum for which Pattern === element. If the optional block is supplied, each matching element is passed to it, and the block’s result is stored in the output array.

重要的部分是此方法 return 的元素,这些元素计算 Pattern === elementtrue。但是 /M/ === {name: 'John', grade: 8, gender: 'M'} 不 return true,数组中的所有其他元素也是如此。

因此你的结果集首先是空的。

块 - { |gend| gend[:gender] } 在你的例子中 - 仅在模式匹配时和之后进行评估。该块会更改整个调用的 return 值,但不会更改模式匹配的完成方式。

请注意 Rexgxp#=== 在这种情况下的文档。

I am creating a method that will filter for a gender of "M" and return its elements.

考虑到上述要求,您的命名似乎具有误导性:

def is_male(gender)
  # ...
end

以上看起来像是一种获取性别并检查其是否为男性的方法。我期待这样的事情:

is_male('M') #=> true
is_male('F') #=> false

is_male(students)也不清楚——它是否检查给定数组中是否有男学生?或者如果所有学生都是男性?无论哪种方式,它听起来都不像过滤。


让我们首先重命名该方法及其参数以更符合您的要求:

def male_students(students)
  # ....
end

如果你想使用grep,你必须提供一个模式,它是一个响应===的对象。正则表达式不起作用,因为它对字符串进行操作,而我们的学生是散列(更多内容见下文)。您 可以 使用 Proc 而不是它也响应 ===:

def male_students(students)
  is_male = ->(student) { student[:gender] == 'M' }
  students.grep(is_male)
end

但是更容易使用select:

def male_students(students)
  students.select { |student| student[:gender] == 'M' }
end

另一种选择是为您的学生使用自定义 class,因为现在,您的学生只是散列:

class Student
  attr_accessor :name, :grade, :gender

  def initialize(name:, grade:, gender:)
    @name = name
    @grade = grade
    @gender = gender
  end

  def male?
    gender == 'M'
  end

  def female?
    gender == 'F'
  end
end

现在,相应地更改数组:

students = [
  Student.new(name: 'John', grade: 8, gender: 'M'),
  Student.new(name: 'Sarah', grade: 12, gender: 'F'),
  Student.new(name: 'Bob', grade: 16, gender: 'M'),
  Student.new(name: 'Johnny', grade: 2, gender: 'M'),
  Student.new(name: 'Ethan', grade: 4, gender: 'M'),
  Student.new(name: 'Paula', grade: 8, gender: 'F'),
  Student.new(name: 'Donald', grade: 5, gender: 'M'),
  Student.new(name: 'Jennifer', grade: 13, gender: 'F'),
  Student.new(name: 'Courtney', grade: 15, gender: 'F'),
  Student.new(name: 'Jane', grade: 9, gender: 'F'),
]

你可以使用这个非常简单的语法:

students.select(&:male?)
#=>
# [
#   #<Student:0x00007fb18d826ce8 @name="John", @grade=8, @gender="M">,
#   #<Student:0x00007fb18d826b58 @name="Bob", @grade=16, @gender="M">,
#   #<Student:0x00007fb18d826a90 @name="Johnny", @grade=2, @gender="M">,
#   #<Student:0x00007fb18d8269c8 @name="Ethan", @grade=4, @gender="M">,
#   #<Student:0x00007fb18d826838 @name="Donald", @grade=5, @gender="M">
#  ]

Array/Enumerable中的任何其他有用方法:

students.any?(&:male?)  #=> true
students.all?(&:male?)  #=> false
students.count(&:male?) #=> 5