Ruby 枚举的使用详解

作者:袖梨 2022-06-25

通过  each 获得枚举能力

想要枚举的 Class 就得有一个 each 方法,它的工作就是 yield 项目给代码块,每次一个。

each 做的事在不同的类上可能不太一样,比如在数组上,each 会 yield 第一个项目,然后是第二个,第三个 ...  在 hash 里,yield 的是 key/value 作为两个元素的数组。在 file 处理上,每次会 yield 一行内容。range 迭代会先看一下有没有可能迭代,然后假装它是一个数组。在自己的类里定义了 each 方法,它的意思是你自己定义的,只要它 yield 点东西就行。

先写个类,理解一下 Enumerable 是怎么回事。Rainbow 彩虹,每次 yield 一种颜色:

class Rainbow
  include Enumerable

  def each
    yield 'red'
    yield 'orange'
    yield 'yellow'
    yield 'green'
    yield 'blue'
    yield 'indigo'
    yield 'violet'
  end
end
用一下 each 方法:

r = Rainbow.new
r.each do |color|
  puts "下个颜色:#{color}"
end
执行的结果:

下个颜色:red
下个颜色:orange
下个颜色:yellow
下个颜色:green
下个颜色:blue
下个颜色:indigo
下个颜色:violet
Rainbow 里混合了 Enumerable 模块,这样 Rainbow 的实例就自动会拥有一大堆基于 each 创建的方法。

find,代码块里返回 true 就会返回第一个元素。比如找到第一个 y 开头的颜色:

y_color = r.find {|color| color.start_with?('y')}
puts "第一个用 y 开头的颜色是:#{y_color}"
结果是:

第一个用 y 开头的颜色是:yellow
find 会调用 each,each  yield 项目,find 在代码块里每次测试一个项目。当 each yield yellow 的时候,find 的代码块通过了测试,y_color 的值就会是 yellow 。

看看 Enumerable 里都有啥:

>> Enumerable.instance_methods(false).sort
=> [:all?, :any?, :chunk, :chunk_while, :collect, :collect_concat, :count, :cycle, :detect, :drop, :drop_while, :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object, :entries, :find, :find_all, :find_index, :first, :flat_map, :grep, :grep_v, :group_by, :include?, :inject, :lazy, :map, :max, :max_by, :member?, :min, :min_by, :minmax, :minmax_by, :none?, :one?, :partition, :reduce, :reject, :reverse_each, :select, :slice_after, :slice_before, :slice_when, :sort, :sort_by, :take, :take_while, :to_a, :to_h, :to_set, :zip]
枚举布尔查询

有些枚举方法会根据一个或多个元素匹配特定的标准返回 true 或 false。

试一下这些:

>> provinces = ["山东", "山西", "黑龙江"]
=> ["山东", "山西", "黑龙江"]
>> provinces.include?("山东")
=> true
>> provinces.all? {|province| province =~ /山/}
=> false
>> provinces.any? {|province| province =~ /山/}
=> true
>> provinces.one? {|province| province =~ /黑/}
=> true
>> provinces.none? {|province| province =~ /河/}
=> true
继续实验:

>> provinces = {'山东' => '鲁', '山西' => '晋', '黑龙江' => '黑'}
=> {"山东"=>"鲁", "山西"=>"晋", "黑龙江"=>"黑"}
>> provinces.include?("山东")
=> true
>> provinces.all? {|province, abbr| province =~ /山/}
=> false
>> provinces.one? {|province, addr| province =~ /黑/}
=> true
也可以换个方法:

>> provinces.keys.all? {|province, abbr| province =~ /山/}
=> false
hash

each 迭代一个 hash ,hash 会被 yield 到你的代码块里,每次一个 key/value 对。每对是一个两个元素的数组。

range

>> r = Range.new(1, 10)
=> 1..10
>> r.one? {|n| n == 5}
=> true
>> r.none? {|n| n % 2 == 0}
=> false
>> r = Range.new(1.0, 10.0)
=> 1.0..10.0
>> r.one {|n| n == 5}
NoMethodError: undefined method `one' for 1.0..10.0:Range
Did you mean? one?
 from (irb):402
 from /usr/local/bin/irb:11:in `'
>> r = Range.new(1, 10.3)
=> 1..10.3
>> r.any? {|n| n > 5}
=> true
枚举搜索与选择

基于一个或多个标准去过滤一个集合的对象。基于一个或多个标准在一个集合的对象里选择项目。我们看一下过滤与搜索的方法,它们都是迭代器,它们都期待你提供一个代码块。在代码块里定义选择标准。

find 第一个匹配

find 或 detect。例:一个整数的数组里找到第一个大于 5 的数字:

>> [1,2,3,4,5,6,7,8,9,10].find {|n| n > 5 } => 6
find 会迭代整个数组,每回都会 yield 一个元素给代码块。方法返回 true,被 yield 的元素就赢了,然后停止迭代。

元素不能通过代码块的测试,方法会返回 nil 。

find_all 与 reject

find_all 又名 select 。返回的新集合里包含匹配的所有的项目。没找到东西会返回空白的集合对象。

>> a = [1,2,3,4,5,6,7,8,9,10]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>> a.find_all {|item| item >5}
=> [6, 7, 8, 9, 10]
>> a.select {|item| item > 100}
=> []
reject

>> a.reject {|item| item > 5}
=> [1, 2, 3, 4, 5]
grep

Enumerable#grep 方法,基于 === 操作符来选择。

>> colors = %w{ red orange yellow green blue indigo violet }
=> ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
>> colors.grep(/o/)
=> ["orange", "yellow", "indigo", "violet"]
再试试:

>> miscellany = [75, 'hello', 10..20, 'goodbye']
=> [75, "hello", 10..20, "goodbye"]
>> miscellany.grep(String)
=> ["hello", "goodbye"]
>> miscellany.grep(50..100)
=> [75]
enumerable.grep(expression) 的功能相当于:

enumerable.select {|element| expression === element }
为 grep 提供一个代码块:

>> colors = %w{ red orange yellow green blue indigo violet }
=> ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
>> colors.grep(/o/) {|color| color.capitalize }
=> ["Orange", "Yellow", "Indigo", "Violet"]
语法:

enumerable.grep(expression) {|item| ... }
相当于:

enumerable.select {|item| expression === item}.map {|item| ... }
group_by

分组,试一下:

>> colors = %w{ red orange yellow green blue indigo violet }
=> ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
>> colors.group_by {|color| color.size}
=> {3=>["red"], 6=>["orange", "yellow", "indigo", "violet"], 5=>["green"], 4=>["blue"]}
partition

>> colors.partition {|color| color.size > 5}
=> [["orange", "yellow", "indigo", "violet"], ["red", "green", "blue"]]
一个 Person 类,每个都有年龄,里面有个 teenager? 实例方法,如果人的年龄在 13 - 19 之间就返回 true。

class Person
  attr_accessor :age
  def initialize(options)
    self.age = options[:age]
  end
  def teenager?
    (13..19) === age
  end
end
生成一组人:

>> people = 10.step(25,3).map {|i| Person.new(:age => i)}
=> [#




































<











































































<<






























&:upcase

































































<





<

<
<

<
<
<

<


<

<

<







<<
<<
<<

<<










<<








<<















<<


















<<







































<<


<<










































<&:upcase


<&:upcase
































相关文章

精彩推荐