通过 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