假设我有一个ruby数组Dates,比如:

2011-01-20
2011-01-23
2011-02-01
2011-02-15
2011-03-21

创建一个按年份和月份对日期元素进行分组的散列的简单方法是什么?
{
    2011 => {
        1   => [2011-01-20, 2011-01-23],
        2   => [2011-02-01, 2011-02-15],
        3  => [2011-03-21],
    }
}

我可以通过遍历所有内容,提取年份、月份等等,然后将它们归来来来实现这一点。
ruby为数组和散列提供了很多方法和块,一定有更简单的方法吗?

最佳答案

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)>]}]]

这样我们就得到了第二级分组。现在,我们不再使用一年中所有日期的数组,而是使用键为months的散列,并为给定月份的日期数组赋值。
唯一的问题是map返回的是数组而不是散列。这就是为什么我们用Hash[]来“包围”整个表达式,在我们的例子pairs[year, hash_of_dates_by_month]中,这会对pairs数组进行散列。
抱歉,如果解释听起来很混乱,我发现解释函数表达式比解释命令式更难,因为嵌套。:(

10-01 19:27
查看更多