Ruby 2.3 给 Enumerable 增加了两个新方法 grep_v
和 chunk_while
。
grep_v
通过 grep
查询可以从一组集合中筛选出符合条件的对象集合。条件表达式是 some_object === item
。
===
以前的文章已经介绍过几次了,这里不多说。
我们可以通过 select
方法来筛选符合条件的对象集合,它内部的行为和 ===
一样:
1.upto(20).select { |i| (6..10) === i } # => [6, 7, 8, 9, 10]
但是现在可以通过 grep
简写成:
1.upto(10).grep(6..8) # => [6, 7, 8]
grep_v
则取相反的集合:
1.upto(10).grep_v(6..8) # => [1, 2, 3, 4, 5, 9, 10]
也可以接受正则表达式作为过滤参数:
MONTHS.grep(/er$/) # => ["September", "October", "November", "December"]
和 case 表达式的高级技巧 介绍的一样,可以通过给任何类或对象定义 ===
来提高 grep
和 grep_v
的灵活性。
chunk_while
Enumerable 模块提供了一些方法用来枚举相邻的元素。比如:slice_when
,slice_before
,slice_after
,each_cons
以及可能少有人用的 chunk
方法。Ruby 2.3 又带来了新的方法 chunk_while
。
在介绍 chunk_while
之前先来看看 chunk
和 slice_when
。
首先我们利用 Montrose gem 来创建一些重复的数据方便随后的测试:
require "montrose"
r = Montrose.every(2.weeks, on: :tuesday, at: '12pm')
r.take(10).to_a
#=>
[2016-02-02 12:00:00 -0500,
2016-02-16 12:00:00 -0500,
2016-03-01 12:00:00 -0500,
2016-03-15 12:00:00 -0400,
2016-03-29 12:00:00 -0400,
2016-04-12 12:00:00 -0400,
2016-04-26 12:00:00 -0400,
2016-05-10 12:00:00 -0400,
2016-05-24 12:00:00 -0400,
2016-06-07 12:00:00 -0400]
对于日历来说,我们可能需要把这些数据按照月份分组。用 group_by
将每周二合并成数组再按照月份分组,将结果变成 hash 并返回:
r.take(10).group_by(&:month)
#=>
{2=>[2016-02-02 12:00:00 -0500, 2016-02-16 12:00:00 -0500],
3=>[2016-03-01 12:00:00 -0500, 2016-03-15 12:00:00 -0400, 2016-03-29 12:00:00 -0400],
4=>[2016-04-12 12:00:00 -0400, 2016-04-26 12:00:00 -0400],
5=>[2016-05-10 12:00:00 -0400, 2016-05-24 12:00:00 -0400],
6=>[2016-06-07 12:00:00 -0400]}
chunk
方法和 group_by
很像,它也接受一个 block/proc
不过返回的是 enumerator 而不是 hash:
r.take(10).chunk(&:month) #=> #<Enumerator: ...>
如果想得到和 group_by
一样的结果,可以利用 Hash[]
构造:
Hash[r.take(10).chunk(&:month).to_a]
#=>
{2=>[2016-02-02 12:00:00 -0500, 2016-02-16 12:00:00 -0500],
3=>[2016-03-01 12:00:00 -0500, 2016-03-15 12:00:00 -0400, 2016-03-29 12:00:00 -0400],
4=>[2016-04-12 12:00:00 -0400, 2016-04-26 12:00:00 -0400],
5=>[2016-05-10 12:00:00 -0400, 2016-05-24 12:00:00 -0400],
6=>[2016-06-07 12:00:00 -0400]}
或者直接将结果转换成数组:
r.take(10).chunk(&:month).to_a
#=>
[[2, [2016-02-02 12:00:00 -0500, 2016-02-16 12:00:00 -0500]],
[3, [2016-03-01 12:00:00 -0500, 2016-03-15 12:00:00 -0400, 2016-03-29 12:00:00 -0400]],
[4, [2016-04-12 12:00:00 -0400, 2016-04-26 12:00:00 -0400]],
[5, [2016-05-10 12:00:00 -0400, 2016-05-24 12:00:00 -0400]],
[6, [2016-06-07 12:00:00 -0400]]]
如果我们只想按照时间分组,而不含有月份的数据:
r.take(10).group_by(&:month).values
#=>
[[2016-02-02 12:00:00 -0500, 2016-02-16 12:00:00 -0500],
[2016-03-01 12:00:00 -0500, 2016-03-15 12:00:00 -0400, 2016-03-29 12:00:00 -0400],
[2016-04-12 12:00:00 -0400, 2016-04-26 12:00:00 -0400],
[2016-05-10 12:00:00 -0400, 2016-05-24 12:00:00 -0400],
[2016-06-07 12:00:00 -0400]]
也可以用 slice_when
来代替这一操作,
r.take(10).slice_when { |a, b| a.month != b.month }.to_a
#=>
[[2016-02-02 12:00:00 -0500, 2016-02-16 12:00:00 -0500],
[2016-03-01 12:00:00 -0500, 2016-03-15 12:00:00 -0400, 2016-03-29 12:00:00 -0400],
[2016-04-12 12:00:00 -0400, 2016-04-26 12:00:00 -0400],
[2016-05-10 12:00:00 -0400, 2016-05-24 12:00:00 -0400],
[2016-06-07 12:00:00 -0400]]
需要注意的是 slice_when
和 chunk
一样返回的是 enumerator。
而 chunk_while
和 slice_when
的意义正好相反:
r.take(10).chunk_while { |a, b| a.month == b.month }.to_a
#=>
[[2016-02-02 12:00:00 -0500, 2016-02-16 12:00:00 -0500],
[2016-03-01 12:00:00 -0500, 2016-03-15 12:00:00 -0400, 2016-03-29 12:00:00 -0400],
[2016-04-12 12:00:00 -0400, 2016-04-26 12:00:00 -0400],
[2016-05-10 12:00:00 -0400, 2016-05-24 12:00:00 -0400],
[2016-06-07 12:00:00 -0400]]
相关阅读