What's new in Ruby 2.3 Enumerable

Ruby 2.3 给 Enumerable 增加了两个新方法 grep_vchunk_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 表达式的高级技巧 介绍的一样,可以通过给任何类或对象定义 === 来提高 grepgrep_v 的灵活性。

chunk_while

Enumerable 模块提供了一些方法用来枚举相邻的元素。比如:slice_whenslice_beforeslice_aftereach_cons 以及可能少有人用的 chunk 方法。Ruby 2.3 又带来了新的方法 chunk_while

在介绍 chunk_while 之前先来看看 chunkslice_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_whenchunk 一样返回的是 enumerator。

chunk_whileslice_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]]

相关阅读

如果觉得我的文章对您有用,请在支付宝公益平台找个项目捐点钱。 @Victor Jan 21, 2016

奉献爱心