require 'date'
dates = [
'2011-01-20',
'2011-01-23',
'2011-02-01',
'2011-02-15',
'2011-03-21'
].map{|sd| Date.parse(sd)}
Hash[
dates.group_by(&:year).map{|y, items|
[y, items.group_by{|d| d.strftime('%B')}]
}
]
#=> {2011=>{"January"=>[#<Date: 2011-01-20 (4911163/2,0,2299161)>, #<Date: 2011-01-23 (4911169/2,0,2299161)>], "February"=>[#<Date: 2011-02-01 (4911187/2,0,2299161)>, #<Date: 2011-02-15 (4911215/2,0,2299161)>], "March"=>[#<Date: 2011-03-21 (4911283/2,0,2299161)>]}}
我注意到您已将月份名称更改为数字,因此您可能需要替换d.strftime('%B')
上面有d.month
或其他什么。
这是一步一步的解释:
您本质上需要两级分组:第一级按年分组,第二级按月分组。 Ruby 有一个非常有用的方法group_by
,它按给定表达式(块)对元素进行分组。所以:第一部分是将原始数组分组year
:
hash_by_year = dates.group_by(&:year)
# => {2011=>[#<Date: 2011-01-20 (4911163/2,0,2299161)>, #<Date: 2011-01-23 (4911169/2,0,2299161)>, #<Date: 2011-02-01 (4911187/2,0,2299161)>, #<Date: 2011-02-15 (4911215/2,0,2299161)>, #<Date: 2011-03-21 (4911283/2,0,2299161)>]}
这给了我们第一层:键是年份,值是给定年份的日期数组。但我们仍然需要对第二层进行分组:这就是为什么我们按年份映射哈希 - 将其值分组month
。让我们开始忘记strftime
并说我们的分组依据d.month
:
hash_by_year.map{|year, dates_in_year|
[year, dates_in_year.group_by(&:month)]
}
# => [[2011, {1=>[#<Date: 2011-01-20 (4911163/2,0,2299161)>, #<Date: 2011-01-23 (4911169/2,0,2299161)>], 2=>[#<Date: 2011-02-01 (4911187/2,0,2299161)>, #<Date: 2011-02-15 (4911215/2,0,2299161)>], 3=>[#<Date: 2011-03-21 (4911283/2,0,2299161)>]}]]
这样我们就得到了第二级分组。现在我们不再使用一年中所有日期的数组,而是使用以月份为键的哈希值以及给定月份的日期数组。
我们唯一的问题是map
返回一个数组而不是哈希值。这就是为什么我们“包围”整个表达式Hash[]
,它从对数组中生成哈希,在我们的例子中是对[year, hash_of_dates_by_month]
.
抱歉,如果解释听起来令人困惑,我发现由于嵌套,解释函数表达式比命令式更难。 :(